Программирование на Ассемблере
Основные определения
Системное программное обеспечение (СПО)– комплекс программ для увеличения производительности вычислительной системы и пользователя. Примером СПО является операционная система. Компонентом СПО является системная программа.
Цель курса.
Изучить языки для написания системных программ.
Изучить приемы и методы создания системных программ
Характеристика языков системного программирования
Язык системного программирования должен удовлетворять следующим свойствам:
должен обеспечить создание эффективной программы по требуемым ресурсам (памяти, времени процессора, дисковое пространство)
обеспечить возможность использования всех особенностей компьютера, например доступ ко всей оперативной памяти, работу в защищенном режиме, использование и управление кэш - памятью и т.д.
некоторые программы, например драйверы для управления внешними ресурсами имеют специальную структуру. Язык должен обеспечить возможность создания программ произвольной структуры.
должен быть надежным и наглядным для уменьшения вероятности пропуска ошибок в программе.
В настоящее время нет языка, полностью удовлетворяющего этим свойствам. Всем требованиям, кроме последнего, удовлетворяет машинный язык и близкий к нему язык Ассемблера. Последнему требованию удовлетворяют языки высокого уровня, но они не удовлетворяют первым трем требованиям, поэтому при создании системных программ используют и язык Ассемблер и язык высокого уровня (язык С, С++). И, хотя для систем, поддерживающих работу с процессорами разных типов, например, WINDOWS NT, драйверы пишутся на языке высокого уровня, значимость ассемблера не падает, так как знание принципов выполнения команд и их хранение в памяти помогает писать «хорошие» программы на любом языке.
Структура 4-х адресной команды
| Код операции | 1 данное | 2 данное | Результат | Адрес следующей команды |
Большинство команд выполняетя в том порядке, в котором они записаны в памяти (естественный порядок выполнения команды), поэтому задание четвертого адреса в большинстве команд не требуется. Так как переменная адресность для первых компьютеров не поддерживалась, вместо 4-х адресных стали использовать команды 3-х адресные. Структура 3-х адресной команды
| Код команды | 1 данное | 2 данное | Результат |
Для реализаций разветвлений в программе в систему команд машины должны быть добавлены команды безусловного и условного перехода, т.е. уменьшение длины команды привело к необходимости увеличения числа команд.
С увеличением общей памяти увеличивается размер адреса, т.к. необходимо под адрес отводить место, достаточное для записи максимального адреса. В этом случае увеличивается размер команды и всей программы. Кроме того при составлении программ очень часто результат записывается вместо одного из исходных данных. В этом случае адреса результата и одного из данных совпадают и можно задать только один адрес. Получаем 2-х адресную структуру команды.
Структура 2-х адресной команды
| Код операции | 1 данное ( результат) | 2 данное |
Или
| Код операции | 1 данное | 2 данное (результат) |
В литературе есть обоснование и первого и второго форматов команд. В настоящее время используются оба формата для разных типов процессоров. Т.к. результат помещается вместо одного из данных, могут потребоваться команды пересылки данных для создания их копии.
Заметим, что операторы языка С вида <Переменная> <Знак операции> <Выражение> как раз отражают эту ситуацию и программируются одной командой после вычисления <Выражения>. Оператор вида <Переменная> = <Переменная> <Знак операции> <Выражение> программируется несколькими командами (почему?).
Доступ к памяти требует значительно больше времени, чем выполнение операции процессором. Для уменьшения потерь памяти используется фиксированная ячейка, называемая Accumulator, доступ к которой значительно быстрее, чем к обычной ячейке памяти за счет того, что она одна и за счет аппаратной реализаци. В этом случае в команде задается только один адрес, второе данное и результат получаются в фиксированной ячейке.
Структура 1 адресной команды.
| Код операции | Адрес данного |
Заметим, что некоторые команды требуют задать только один адрес без использования аккумулятора, например, команда увеличения на 1 содержимого памяти.
Так оператор С x++ означает использование одноадресной команды вместо двухадресной сложения (x+=1) или нескольких команд в случае x=x+1.
В систему команд должны быть добавлены команды для обмена данными между аккумулятором и памятью. Недостаток одноадресных команд - основную часть программы составляют команды пересылки данных.
В современных процессорах вместо одной фиксированной ячейки (регистра) используется несколько, в этом случае в команде задается адрес данного и регистр, т.е. опять возвращаемся к двух адресной структуре команды.
Структура безадресных команд
Некоторые из команд не требуют задания адреса, например, команда СТОП (Halt) для процессора. В случае использования команд с данными можно использовать стек для хранения данных, в этом случае адреса данных можно не задавать. Стек – это массив, заполнение и извлечение данных для которого выполняется по правилу «Первый вошел, последний вышел». В этом случае данные для операции записываются в стек. Результат помещается вместо этих данных.
Сравнительный анализ команд с различной адресностью для современных процессоров
Современные процессоры имеют переменную адресность команд. Анализ структур команды современных процессоров показывает, что большинство команд двухадресные. Некоторые команды могут быть одноадресными (x++) или трех адресными (например, команда длинного сдвига), а иногда и безадресными (команды FPU). Вместо адресов данных могут использоваться константы (литералы). В качестве адресов могут задаваться регистры, в этом случае скорость выполнения команд увеличивается. Большинство современных процессоров требует, чтобы одно из данных в двух адресной структуре команды было в регистре обязательно, это сокращает длину команды и время ее выполнения.
Для экономии памяти под команды код команды имеет переменную длину от 4 бит до 2-х байт для процессоров INTEL
История развития ассемблера. Характеристика машинного языка
Первые компьютеры «знали» один язык – машинный. Рассмотрим характеристики этого языка. Конструкциями машинного языка являются константы и команды. Команды содержат код команды и адреса данных, которые используются в командах.
Система команд
Пусть используется 3-х адресная система команд с кодами операций, заданными в табл. 1.1
Таблица 1.1 Система команд для 3-х адресной машины
| Операция | Код | ||
| + | 1 | ||
| - | 2 | ||
| * | 3 | ||
| / | 4 |
Распределение памяти
При распределении памяти необходимо поставить в соответствие адреса ячеек памяти исходным данным и результатам программы. В табл. 2.1 представлено распределение памяти для заданного примера.
Таблица 1.2 Распределение памяти
| Переменная | Адрес памяти | ||
| X | 0 | ||
| Y | 1 | ||
| Z | 2 | ||
| U | 3 | ||
| V | 4 | ||
| W | 5 |
Программа вычисления
Программа представлена в табл. 1.3
Таблица 3.1 Программа для 3-х адресной машины
| Адрес | Код | 1 данное | 2 данное | Результат | Комментарий | ||||||
| 6 | 01 | 0 | 1 | 20 | X + Y ®20 | ||||||
| 7 | 02 | 20 | 2 | 21 | X+Y-Z®21 | ||||||
| 8 | 03 | 21 | 3 | 21 | (X+Y-Z)*U®21 | ||||||
| 9 | 01 | 21 | 4 | 21 | (X+Y-Z)*U + V®21 | ||||||
| 10 | 04 | 21 | 20 | 5 | Результат |
Для эффективного распределения памяти программа должна быть переписана с использованием 11 и 12 ячеек в качестве промежуточных.
Пример программирования в машинных кодах.
Пусть необходимо составить программу для вычисления значения выражения:
W = ((X + Y – Z) * U + V)/(X + Y).
Характеристика языков ассемблерного типа
Для упрощения распределения памяти и запоминания кодов команд вместо машинных кодов используются их обозначения, а вместо конкретных адресов – символические адреса. Символические коды для основных операций заданы в табл. 4.1
Таблица 4.1 Мнемонические коды арифметических команд
| Код | Обозначение | ||
| 01 (+) | ADD | ||
| 02 (-) | SUB | ||
| 03 (*) | MUL | ||
| 04 (/) | DIV |
Пусть данные занимают ячейки
D + 0 : X D + 1: Y D + 2 : Z D + 3 : U D + 4 : V
D + 5 : W
Пусть программа занимает ячейки P + 0, P + 1, …
Пусть в качестве ячеек для промежуточных данных используются R + 0, R + 1, …
Текст программы с учетом принятых обозначений задан в табл. 5.1.
Таблица 5.1. Текст программы
| Адрес | Код | 1 данное | 2 данное | Результат | Комментарий | ||||||
| P+0 | ADD | D+0 | D+1 | R+0 | X + Y ®R+0 | ||||||
| P+1 | SUB | R+0 | D+2 | R+1 | X+Y-Z®R+1 | ||||||
| P+2 | MUL | R+1 | D+3 | R+1 | (X+Y-Z)*U®R+1 | ||||||
| P+3 | ADD | R+! | D+4 | R+1 | (X+Y-Z)*U + V®R+1 | ||||||
| P+4 | DIV | R+1 | R+0 | D+5 | Результат |
Пусть D= 0. В этом случае программа начинается с ячейки после D + 5, т.е. с ячейки 6. (P = 6). Промежуточные данные можно располагать, начиная с P + 5, т.е. R = 11. Преобразование кодов и адресов в машинные коды и адреса выполняется специальной программой.
Язык, в котором вместо машинных кодов используются их символические обозначения, а вместо абсолютных – относительные адреса, называется языком символического кодирования или ассемблером.
Ассемблер – язык, для которого одна команда преобразуется в одну команду машинного языка. Исключением являются макросы, в этом случае одной макрокоманде может соответствовать более одной машинной команды.
Для современных компиляторов вместо относительных адресов можно использовать обозначение переменных, например ADD X, Y, R. Для вычисления адреса можно использовать более сложные выражения
Пример программы на ассемблере для 16 битного приложения (DOS приложение)
ideal
MODEL SMALL
DATASEG
; Распределение памяти
X DW 5; Исходные данные
Y DW 6
Z DW ?; Результат
CODESEG
; Программа
begin:
; Связь с данными
mov ax, @data
mov ds, ax
; Выполнение требуемых операций
mov ax, [x]
add ax,[y]
mov [z], ax
; Выход из программы
mov ax, 4c00h
int 21h
;Конец программы
end begin
Трансляция, компоновка и загрузка в отладчик программы выполняется с помощью команд:
TASM /zi <Имя>.asm
TLINK /v < Имя >.obj
TD < Имя >
При написании командного файла предполагается, что программы для обработки файла (tasm, tlink, td) находятся в доступном каталоге. Недостатки приведенного командного файла:
несмотря на наличие ошибок, все этапы выполняются;
командный файл можно использовать только для работы с заданным файлом (с заданным именем).
Для исправления этих недостатков рекомендуем использовать командный файл:
TASM /zi %1
IF ERRORLEVEL 1 GOTO m1
TLINK /v %1
IF ERRORLEVEL 1 GOTO m1
TD %1
:m1
Оператор IF ERRORLEVEL 1 GOTO m1 означает, что выполняется переход на метку m1, если код возврата исполняемой программы равен 1 или выше. Все системные программы возвращают 0 при успешном завершения и число, не равное 0 для завершения с ошибками[1]. Поэтому данная проверка позволяет проверить наличие ошибок при компиляции и компоновке. Обратите внимание на синтаксис записи метки на языке команд операционной системы!
Пример программы на ассемблере для 32 - битного приложения
ideal
p586
MODEL FLAT
EXTRN ExitProcess : proc
DATASEG
X DW 5
Y DW 6
Z DW ?
CODESEG
begin:
mov ax, X
add ax,Y
mov Z, ax
call ExitProcess
end begin
Модель FLAT соответствует 32-битному режиму, когда для задания адреса используется 32-битное число, и диапазон изменения адреса равен 0.. 232-1. Эта модель работает только для 32 битных процессоров, что задается директивой p586.Программа использует функцию операционной системы для завершения программы. Для выполнения этой операции используется функция ExitProcess. Все функции операционной системы (функции WINDOWS API) откомпилированы в регистро – чувствительном режиме.
Командный файл для 32 – битного режима:
TASM32 /ml /zi %1
IF ERRORLEVEL 1 GOTO m1
TLINK32 /v %1 import32.lib
IF ERRORLEVEL 1 GOTO m1
TD32 %1
:m1
Заметим, что регистро - чувствительная компоновка (флаг /ml) для 32-битного варианта обязательна. Функция ExitProcess предназначена для завершения процесса.
Как показывает сравнительный анализ программ существуют незначительные отличия для программ разных типов. В данном курсе будут рассматриваться программы для 32-битных приложений.
Основные команды для работы с битами
Основные команды заданы в табл. 10.1
Таблица 10.1. Основные команды для работы с битами
| Но-мер | Назначение | Код | Выполняемые действия | Формируемые флаги | |||||
| 1 | Побитовое сложение | OR | оп1|=оп2 | c=0, o=0, z, p, s | |||||
| 2 | Побитовое умножение | AND | оп1&=оп2 | c=0, o=0, z, p, s | |||||
| 3 | Проверка | TEST | оп1&оп2 | c=0, o=0, z, p, s | |||||
| 4 | Побитовое Отрицание | NOT | ~оп1 | Флаги не изменяются | |||||
| 5 | Сложение по модулю 2 | XOR | оп1^=оп2 | c=0, o=0, z, p, s |
Примеры использования команд.
Пример1. Записать команды для:
установки в 1 заданного бита в байте;
установки в 0 заданного бита в слове;
инвертирования заданного бита в двойном слове;
проверки заданного бита в двойном слове;
Пусть номер бита является константой
BitNumber EQU 3
b db 37h
w dw 1234h
d1 dd 12345678h
d2 dd 12345678h
...
; установка в 1 заданного бита в байте
OR b, 1 SHL BitNumber
; установка в 0 заданного бита в слове
AND w, NOT (1 SHL BitNumber)
; инвертирование заданного бита в двойном слове
XOR d1, 1 SHL BitNumber
проверка заданного бита в двойном слове
TEST d2, 1 SHL BitNumber
jz zero
...
zero:
Пример 2.
Задан массив байтов. Переписать в другой массив те байты первого массива, в которых биты 0 и 4 единичные, биты 1, 3, 7 - нулевые, значения остальных битов не имеет значения.
Для решения этой задачи сформируем маски для выделения требуемых битов и проверки заданных битов на 1. Первая маска может быть сформирована так:
(1 shl 0) or (1 shl 4) or (1 shl 1) or (1 shl 3) or (1 shl 7).
Вторая маска имеет вид:
(1 shl 0) or (1 shl 4).
Ideal
p386
model flat
extrn ExitProcess:proc
dataseg
a db 11h, 12h, 23h, 34h, 56h, 78h, 90h, 0a1h, 0b2h, 0c3h, 0d4h, 0e5h
count dd 11
b db 11 dup (0)
codeseg
begin:
mov ecx, [count]
mov eax, 0; индекс исходного массива
mov edx, 0 ;индекс результирующего массива
fori:
mov bl, [a+eax]
mov bh, bl
and bl, (1 shl 0) or (1 shl 4) or (1 shl 1) or (1 shl 3) or (1 shl 7)
xor bl, (1 shl 0) or (1 shl 4)
jnz short next
mov [b+edx], bl
inc edx
next:
inc eax
loop fori
call ExitProcess
end begin
В этом примере только первое число удовлетворяет поставленным требованиям.
Пример 3. Вычислить значение булевского выражения.

Значение выражения равно истине, если значение хотя бы одного слагаемого равно истине. Для выделения требуемых битов и проверки единичных битов используются константы:
; Первое слагаемое
c11 db 11010110b
c12 db 10000110b
; Второе слагаемое
c21 db 01101100b
c22 db 01001000b
; Третье слагаемое
c31 db 00000111b
c32 db 00000101b
Пусть вычисляется значения для байта
x db 10101010b
Результат записывается в поле RES и равен 0 для ответа «Ложь» и 1 для ответа «Истина».
RES db ?
; Проверка первого слагаемого
mov RES, 1; Пусть ответ равен «Истина»
mov al, [x]
and al, [c11]
xor al, [c12]
jz short true
; Проверка второго слагаемого
mov al, [x]
and al, [c21]
xor al, [c22]
jz short true
; Проверка третьего слагаемого
mov al, [x]
and al, [c31]
xor al, [c32]
jz short true
mov [RES], 0
true:
Классификация команд
Сдвиг может быть обычным и циклическим. При обычном сдвиге разряды, выходящие за разрядную сетку, уничтожаются. Для циклического сдвига они записываются на место освободившихся разрядов.
Обычный сдвиг может быть арифметическим и логическим. При арифметическом сдвиге вправо свободные позиции слева заполняются знаковым (старшим) разрядом. При логическом сдвиге вправо свободные разряды слева заполняются нулями. Арифметический сдвиг вправо используется обычно для деления нацело знакового числа на 2n, а логический - для беззнакового числа. Операции сдвига влево для обоих типов команд выполняются одинаково.
Циклический сдвиг может быть с переносом и без переноса. Схема выполнения команд для обоих типов сдвига на 1 бит вправо представлен на рис. 10.1a и рис. 10.1b.
а - циклический сдвиг с переносом

b- циклический сдвиг без переноса

Рис. 10.1 Схема выполнения циклического сдвига
Стрелка вверху задает направление сдвига (вправо). Как видно из рисунка, выдвигаемый бит всегда помещается в бит переноса С. При циклическом сдвиге предыдущее содержимое бита переноса записывается в освободившийся разряд, при обычном – теряется, а в освободившийся разряд записывается выдвигаемый бит.
Заметим, что при любом сдвиге последний из выдвинутых разрядов записывается вместо бита переноса.
Кодировка команд сдвига
Первая буква кода определяет тип сдвига. Буква S соответствует обычному, а буква R циклическому сдвигу. Вторая буква уточняет тип сдвига. Для обычного сдвига используется буква A для арифметического и буква H для логического сдвига. Для циклического сдвига вторая буква С
соответствует сдвигу с переносом и буква O сдвигу без переноса. Третья буква определяет направление сдвига. Буква R
соответствует сдвигу вправо, и буква L сдвигу влево. Таким образом коды команд сдвига:

Общий вид команд сдвига
Общий вид команд сдвига:
код оп1, оп2
оп1 - сдвигаемое данное. Может быть байтом. Словом или двойным словом. Может быть задано в общем регистре или памяти.
оп2 - задает, на сколько разрядов выполняется сдвиг. Может быть константой или регистром CL, в который предварительно необходимо записать константу сдвига.
Примеры использования команд сдвига.
Пример 1. Установить в 1 бит i
двойного слова x, если значение i вычисляется в программе.
Mov cl, [i]
mov 1, eax
shl eax, cl
or [x], eax
Пример 2. Задан массив чисел, каждое число не более 32. Упаковать этот массив таким образом, чтобы под каждое число отводилось 5 бит.
Ideal
p386
model flat
extrn ExitProcess:proc
dataseg
x db 3,5,7,9,2,4,6
xpack db (((xpack-x)*8+4)/5) dup (0)
count dd xpack - x
codeseg
begin:
mov eax, [count]
; for (i=0; i< count; i++){
unsigned *p = xpack+(i*5)/8
}
mov esi, 0;
mov ebx, 0
fori:
mov dx, 0
mov ecx, ebx
and ecx, 7
mov dl, [x+esi]
shl dx, cl
mov ecx, ebx
shr ecx, 3
or [xpack+ecx], dx
add ebx, 5
inc esi
cmp esi, [count]
jl fori
call ExitProcess
end begin
Группа команд: проверить и изменить
Команды проверяют значение заданного бита (записывают его содержимое в бит C) и меняют значение в соответствии с заданным кодом. Первый операнд задает проверяемое и изменяемое данное, второй - номер бита. В качестве первого операнда может быть регистр и память размером 1, 2 или 4 байта. Второй операнд задается константой или регистром. Команды для проверки и изменеия заданных битов представлены в табл. 10.2
Таблица 10.2. Команды для проверки и изменеия заданных битов
| Назначение | Код | Выполняемые действия | |||
| Проверка бита | Bt | C=(oп1 & (1<<оп2))>> оп2 | |||
| Проверка и установка бита | Bts | C=(oп1 & (1<<оп2))>> оп2; oп1 |= (1<<оп2)) | |||
| Проверка и сброс бита | Btr | C=(oп1 & (1<<оп2))>> оп2; oп1 &= (~(1<<оп2)) | |||
| Проверка и инвертирование бита | Btc | C=(oп1 & (1<<оп2))>> оп2; oп1 ^= (1<<оп2)) |
Примеры использования команд
Пример 1. Вычислить сумму всех битов, стоящих на четных местах в двойном слове
p486
ideal
model flat
extrn ExitProcess:proc
dataseg
x dd 0a5a5a5a5h
s dd ?
codeseg
begin:
xor edx, edx; s=0
mov eax, [x]
mov ecx, 16
mov ebx, 0
for:
bt eax, ebx
adc edx, 0
add ebx, 2
loop for
mov [s], edx
call ExitProcess
end begin
Программа выдает ответ 8.
Команды длинного сдвига
Общий вид команды:

где оп1 - задает, что сдвигается (память или 32 битный регистр);
оп2 - откуда вдвигаются разряды на освободившиеся места (32 - битный регистр);
оп3 - на сколько сдвиг (константа или регистр CL ).
Схема выполнения команды SHLD представлена на рис. 10.2

Рис. 10.2
Заметим, что содержимое оп2 сдвигается виртуально, фактическое содержимое этого регистра не изменяется.
Пример использования команды.
Задано число длиной 1024 бита. Разделить его на 8.
Для деления числа на 8 достаточно сдвинуть его на 3 бита вправо, в этом случае получим частное т деления. Остаток равен младшим трем битам исходного числа.
p486
ideal
model flat
y=x>>3; ost = x[0]&7
extrn ExitProcess:proc
dataseg
x dd 32 dup (0ffffffffh)
y dd 32 dup (?); Частное
ost dd ? ; Остаток
codeseg
begin:
; y=x
mov ecx, 32
for1:
mov eax, [x+ecx*4-4]
mov [y+ecx*4-4], eax
loop for1
;for (i=0; i<31; i++)
mov ecx, 31
mov eax, 0
; shrd (y[i], y[i+1], 3);
for2:
mov ebx, [y+eax*4+4]
shrd [y+eax*4], ebx, 3
;i++
inc eax
loop for2
;shr (y[i], 3);
shr [y+eax*4], 3
; ost = x[0] & 7
mov eax, [x]
and eax, 7
mov [ost], eax
call ExitProcess
end begin
Дополнительные команды
Следующие две команды позволяют осуществить поиск первого установленного в 1 бита операнда. Поиск можно произвести как с начала так и от конца операнда:
bsf
операнд_1,операнд_2 (Bit Scaning Forward) - сканирование битов вперед.
Команда просматривает (сканирует) биты операнд_2
от младшего к старшему (от бита 0 до старшего бита) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения. Если все биты операнд_2 равны 0, то флаг нуля zf устанавливается в 1, в противном случае флаг zf сбрасывается в 0.
Пример:
| mov al,02h bsf bx,al ;bx=1 jz m1 ;переход, если al=00h ... |
bsr операнд_1,операнд_2
(Bit Scaning Reset) — сканирование битов в обратном порядке. Команда просматривает (сканирует) биты операнд_2
от старшего к младшему (от старшего бита к биту 0) в поисках первого бита, установленного в 1. Если таковой обнаруживается, в операнд_1 заносится номер этого бита в виде целочисленного значения.
При этом важно, что позиция первого единичного бита слева отсчитывается все равно относительно бита 0. Если все биты операнд_2
равны 0, то флаг нуля zf
устанавливается в 1, в противном случае флаг zf
сбрасывается в 0. Заметим, что эти команды для tasm32 не компилируются
Определение типа битовой структуры
Общий вид типа:
record Имя поле1, поле2, ...
Для задания поля используется запись:
Имя: Ширина[= Значение]
Поля задаются, начиная с поля, соответствующего старшим разрядам. Суммарная ширина не может превосходить 32 бит. Если суммарная ширина не равна полному числу байтов, то число располагается в младших разрядах. Старшие разряды обнуляются.
Выделение памяти под битовую структуру
Общий вид директивы выделения памяти:
Тип структуры Имя данного <Значения полей>
{ Значения полей }
Значения полей задаются как для обычной структуры в угловых скобках (позиционный метод) или в фигурных (вместе с именем поля). Если значение поля не задано, оно обнуляется.
Особенности использования битовых структур
Правила:
Если в команде используется имя данного, само
значение данного используется.
Если в команде задано имя поля, то результатом является константа сдвига, которая показывает положение поля в записи.
Если в команде используется операция mask имя поля или mask имя типа, то формируется маска для выделения поля (записи).
Если в команде используется операция width имя поля или width имя типа, то формируется ширина поля (записи) в битах.
Пример использования битовой структуры
Дата создания файла задается в виде:
день : 5 битов;
месяц : 4 бита;
год: 7 битов.
Год задает разность между текущим годом и 1970[13]
годом.
Значения для текущей даты упаковать в структуру и
проверить правильность упаковки.
ideal
p486
model flat
extrn ExitProcess:proc
dataseg
record data1 y:7, m:4, d:5
d1 data1 <>
day db 1
month db 11
year dw 2000
codeseg
begin:
movzx ax, [day]
movzx bx, [month]
mov cl, m
shl bx, cl
or ax, bx
mov bx, [year]
sub bx, 1970
mov cl, y
shl bx, cl
or ax, bx
mov [d1], ax
mov ax, [d1]
mov bx, mask d
and bx, ax
mov [day], bl
mov bx, mask m
and bx, ax
mov cl, m
shr bx, cl
mov [month], bl
mov bx, mask y
and bx, ax
mov cl, y
shr bx, cl
add bx, 1970
mov [year], bx
call ExitProcess
end begin
Использование битовых структур позволяет сделать программу более мобильной. При изменении размера полей достаточно изменить только определение структуры, все остальные команды изменять не надо.
Типы программ
Одномодульная и многомодульная.
Одномодульная программа - в одном файле. Многомодульная состоит из нескольких файлов.
Использование функций
Функции позволяют оформлять независимые участки
программы в виде отдельных модулей, что значительно упрощает отладку программы и уменьшает ее размер. Ниже будут рассмотрены команды для работы с функциями, структура функций и способы передачи параметров. Вначале даются общие положения для функций. Команды для работы с процедурами(функциями)
Основные команды для работы с функциями приведены в табл. 11.1, а директивы в табл. 11.2
Таблица 11.1 Команды для работы с функциями
| Назначение | Общий вид команды | Выполняемые действия | |||
| Вызов функции | call операнд[14] | push EIP MOV EIP, операнд | |||
| Возврат в вызывающую программу | ret [Число] | Esp+=Число Pop EIP |
Таблица 11.2. Директивы для работы с функциями
| Назначение | Общий вид директивы | ||
| Заголовок функции | имя proc или proc имя | ||
| Директива конца | [Имя] endp или endp [Имя] |
Общий вид функции для режима masm:
имя proc
[; Сохранение используемых регистров]
.................
[; Восстановление используемых регистров]
ret
имя endp
Общий вид функции для режима ideal:
proc имя
[; Сохранение используемых регистров]
.................
[; Восстановление используемых регистров]
ret
endp имя
Функция одномодульной программы должна быть частью сегмента кодов. Может быть задано несколько функций последовательно.При выполнении программ переход к функции необходимо выполнять только по команде call, а не в результате естественного выполнения команд, поэтому функции помещаются или в начало сегмента кода до точки входа, или в конце после команд для выхода из программы.
Передача параметров через регистры
Для задания параметров или их адресов используются регистры.
Пример 1. Составить вызывающую программу и функцию для вычисления наибольшего общего делителя для двух целых положительных чисел с помощью алгоритма Эвклида.
Для данной программы исходными данными являются 2 целых числа, результатом является значение наибольшего общего делителя. Параметрами являются значения 2-х чисел и адрес результата. Пусть исходные данные передаются через регисты EAX, EBX, а адрес результата через регистр ECX
IDEAL
p586
MODEL FLAT
extrn ExitProcess:proc
DATASEG
X DD 150
Y DD 120
Z DD ?
CODESEG
begin:
mov eax, [X]
mov ebx, [Y]
mov ecx, offset Z
call gcd
call ExitProcess
; Процедура
proc gcd
push edx
; if (x
jge short m1
xchg eax, ebx
; if (y==0) return x
m1:
test ebx, ebx
je short break
; while(1)
for:
xor edx, edx
div ebx
test edx, edx
je short break
mov eax, ebx
mov ebx, edx
jmp for
break:
mov [ecx], ebx
pop edx
ret
endp gcd
end begin
Достоинство способа - наиболее быстрый способ. Недостаток - недостаточное число регистров.
Пример 2. Составить процедуру для вычисления длины строки с нулевым завершителем. Пусть адрес начала строки в регистре EBX, а
адрес длины строки возвращается через регистр EAX.
; Процедура для вычисления длины строки с нулевым
; завершителем. Пусть адрес начала строки в регистре
; EBX, а адрес длины строки - через регистр EAX.
ideal
P486
MODEL flat
extrn ExitProcess:proc
DATASEG
str1 db 'Это первая строка', 0
str2 db 'А это вторая', 0
n dd ?, ?
CODESEG
begin:
lea ebx, [str1]
lea eax, [n]
call strlen
lea ebx, [str2]
lea eax, [n+4]
call strlen
call ExitProcess
proc strlen
push ecx edx
xor ecx, ecx
for:
mov dl, [ebx] ; while (str[i])ax++;
and dl, dl
je short lend; конец строки
inc ecx
inc ebx
jmp for
lend:
mov [eax], ecx
pop edx ecx
ret
endp strlen
end begin
Недостатки использования регистров для передачи параметров:
1. Можно использовать только для передачи данных длиной 1, 2, 4 байта или адресов:
2. Количество регистров ограничено.
Для преодоления недостатков можно все параметры записать в структуру и передать адрес начала структуры в единственном регистре или использовать для передачи параметров стек.
Передача параметров через общую область памяти
Т.к. в одномодульной программе вызывающая программа и функция расположены в одном файле, функция может непосредственно использовать переменные программы.
Пример.
Составить функцию для обмена местами значения двух переменных.
ideal
P486
MODEL flat
extrn ExitProcess:proc
DATASEG
x dd 5
y dd 3
CODESEG
begin:
call swap
call ExitProcess
proc swap
push eax ebx
mov eax, [x]
mov ebx, [y]
mov [y], eax
mov [x], ebx
pop ebx eax
ret
endp swap
end begin
Недостатки использования переменных из сегмента данных:
1. Функция должна использоваться только для переменных с заданными именами, использовать ее для других переменных нельзя.
2. Этот способ не применим для многомодульных программ
Общей областью можно считать стек, в который помещаются параметры. Рассмотрим особенности передачи параметров через стек.
При передаче параметров через стек возникает проблема доступа к ним. Из всех режимов адресации выберем режим, связанный с адресацией в стеке. В этом режиме в качестве базисного регистра используется регистр EBP, именно этот регистр будем применять для задания элемента стека. С другой стороны, указатель стека, т.е. адрес последнего занесенного элемента в стек расположен в регистре ESP. Чтобы иметь доступ к данным стека используются команды:
push ebp ; сохранение EBP
mov ebp, esp ; EBP=ESP
Эти команды должны присутствовать в любой функции, в кото-
рой параметры передаются через стек.
Передача в списке параметров простых переменных
Параметры можно разделить на параметры - исходные данные и параметры-результаты. Для первых передаются значения, а для вторых - адреса. Чтобы уменьшить число передаваемых параметров, для массивов передаются адреса, а не значения элементов, даже для массивов исходных данных. Чтобы стек не переполнялся, после использования параметры должны быть извлечены из стека. Эту функцию, называемую очисткой стека, обычно выполняет функция . Для этого в команде ret задается количество байтов, используемых для параметров, т.е. используется формат команды ret <число>. В этом случае сначала очищается стек, а затем выполняется возврат в вызывающую программу.
Ниже рассмотрены примеры использования функций с передачей
параметров через стек. Примеры отражают приемы передачи простых переменных (исходных данных и результатов), массивов, структур, и адресов функций.
Пример 1. Составить функцию вычисления z=x+y и использовать ее для вычисления c=a+b; f=d+e.
;Функция для вычисления z=x+y и использовать ее для вычисления c=a+b; f=d+e
IDEAL
P586
MODEL FLAT
EXTRN ExitProcess:proc
DATASEG
a DD 5
b DD 3
c DD ?
d DD 4
e DD -5
f DD ?
CODESEG
begin:
push [a] ; запись в стек a, b
push [b]
lea eax, c ;Формирование & c
push eax ; и запись его в стек
call summa ; Вызов функции
push [d] ; запись в стек d, e
push [e]
lea eax, [f] ; Формирование & f
push eax
call summa ; Вызов функции
call ExitProcess
| proc summa | 0 | EBP | |||||||
| push ebp | 4 | EIP | |||||||
| mov ebp, esp | 8 | &c | |||||||
| Push eax ebx | 12 | B | |||||||
| mov eax, [ebp+16] ; a | 16 | A | |||||||
| Add eax, [ebp+12] ; a+b | |||||||||
| mov ebx, [ebp+8] ; &c | |||||||||
| mov [ebx], eax ; c=a+b | |||||||||
| Pop ebx eax ebp | |||||||||
| Ret 12 | |||||||||
| Endp summa | |||||||||
| End begin |
В примере показана передача простых переменных (исходных данных и результатов). Показано состояние стека после обращения к функции для вычисления c = a + b. Анализ программы показывает, что передача параметров с последующей очисткой стека, передача управления процедуре требует дополнительных команд. Эти же действия выполняются при программировании на языке высокого уровня. Поэтому, прежде чем писать функцию на любом языке, подумайте, эффективна ли она! Решение предыдущего примера без функции явно предпочтительней!
Передача в списке параметров одномерных массивов
Для одномерных массивов – и исходных данных и результатов – передаются адреса. Это исключает необходимость копирования исходных массивов.
Пример 1. Составить функцию для объединения двух строк и использовать ее для объединения строк, содержащих слова "first " и "second " в одну строку.
IDEAL
P586
MODEL FLAT
Extrn ExitProcess:proc
DATASEG
str1 db 'first ', 0
str2 db 'second', 0
rez db (rez-str1-1) dup (?)
CODESEG
begin:
push offset str1 offset str2 offset rez ; передача адресов массивов
call strcat ;вызов процедуры
call ExitProcess
| proc strcat; | 0 | EBP | |||
| push ebp; | 4 | EIP | |||
| mov ebp, esp; | 8 | REZ | |||
| Push eax ebx ecx; | 12 | STR2 | |||
| ; for (I=0; str1[I]; I++) rez[I]=str1[i]; | 16 | STR1 | |||
| Mov EAX, [EBP+16]; | |||||
| Mov EBX, [EBP+8]; | |||||
| For1: | |||||
| Mov CL, [EAX] | |||||
| Test Cl, cl | |||||
| Je break1 | |||||
| Mov [EBX], cl Inc eax Inc ebx | |||||
| Jmp For1 Break1: Mov EAX, [ebp+12] For2: | |||||
| Mov CL, [EAX] | |||||
| Mov [EBX], cl Test Cl, cl | |||||
| Je break2 | |||||
| Inc eax Inc ebx Jmp For2 Break2: |
Pop ecx ebx eax ebp
Ret 12
Endp
End begin
В этом примере признаком конца исходных данных является нулевой завершитель (символ с кодом 0). В результирующую строку завершитель записывается после копирования второй строки. Для массивов исходных данных (str1, str2) и массива результата (rez) передаются адреса. Это делается для экономии стекового простран-
ства и числа команд, необходимых для передачи параметров.
Пример 2. Составить программу для записи в третий массив тех чисел первого, которых нет во втором.
ideal
p386
model flat
extrn ExitProcess:proc
dataseg
x dd 1,2,3,4,5,6
ALаааааааааааааааааа - Ёхчєы№ЄрЄ - срщЄ
AXаааааааааааааааааа - Ёхчєы№ЄрЄ - ёыютю
EAXаааааааааааааааа - Ёхчєы№ЄрЄ Ц фтющэюх ёыютю
EDX, EAXаааааа - Ёхчєы№ЄрЄ Ц 2 фтющэvї ёыютр
аааа ¦ЁшьхЁ 3.а TюёЄртшЄ№ ЇєэъЎш¦ фы ёЁртэхэш "фышээvї" ўшёхы. LєэъЎш фюыцэр тючтЁр•рЄ№ 0, хёыш ўшёыр ёютярфр¦Є, 1 Ц хёыш яхЁтюх ўшёыю сюы№°х тЄюЁюую ш Ц1 т яЁюЄштэюь ёыєўрх.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
xааааааааа ddааааааа 1000 dup (0ffffffffh)
nааааааааа ddааааааа (n-x)/4
yааааааааа ddааааааа 1000 dup (0ffffffffh)
resаааааа ddааааааа ?
t1аааааааа ddааааааа ?
t2аааааааа ddааааааа ?
codeseg
begin:
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp
movаааа [res], eax
rdtsc
subаааааа eax, ecx
movаааа [t1], eax
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp
movаааа [res], eax
rdtsc
subаааааа eax, ecx
movаааа [t1], eax
pushаааа [n] offset x offset yаа
rdtsc
movаааа ecx,eax
callааааа m_cmp1
movаааа [res], eax
rdtsc
subаааа eax,ecx
movаааа [t2], eax
call ExitProcess
;for (i=n-1; i>=0; i--){ааааааааааааааааааааааа ebpааааа 0
;ааа if (x[i]>y[i]) return -1;а ааа eipаааааа 4
;ааа if (x[i]
;return 0;ааааааааааааааааааааа аааааааааааааааааааааа nааааааааа 16
proc ааа m_cmp
push ebp;
movа ebp,а esp;
Pushа ebx ecx edx
movаааа ecx, [ebp+16]; n
movаааа eax, [ebp+12]; &x
movаааа ebx, [ebp+8] ; &y
fori:
movаааа edx, [eax+ecx*4-4]
cmpаааа edx, [ebx+ecx*4-4]
jbаааааааа short m1; x[i]< y[i]
jaаааааааа short m2; x[i]> y[i]
loopаааа fori
subаааааа eax, eax
m1:
movаааа eax, -1
jmpааааа short m3
m2:
movаааа eax, 1
m3:
popааааа edx ecx ebx ebp
Retаааааа 12
Endp
proc ааа m_cmp1
push ebp;
movа ebp,а esp;
Pushаа ecx esi edi
movаааа ecx, [ebp+16]; n
movаааа esi, [ebp+12]; &x
leaаааааа esi, [esi + ecx*4-4]
movаааа edi, [ebp+8] ; &y
leaаааааа edi, [edi + ecx*4-4]
std
repeаааа cmpsd
jbаааааааа short m11; x[i]< y[i]
jaаааааааа short m12; x[i]> y[i]
subаааааа eax, eax
m11:
movаааа eax, -1
jmpааааа short m13
m12:
movаааа eax, 1
m13:
cld
popааааа edi esi ecx ebp
Retаааааа 12
Endp
end begin
¦рьхЄшь, ўЄю лЎшЇЁv¬ ўшёыр ёЁртэштр¦Єё , эрўшэр ёю ёЄрЁ°хщ ЎшЇЁv. ¦рцфр ЎшЇЁр ёЁртэштрхЄё ъръ схччэръютюх ўшёыю. TЁртэхэшх яЁюфюыцрхЄё фю Єхї яюЁ, яюър эрщфхь Ёрчэvх ЎшЇЁv шыш эх ёЁртэшь тёх ЎшЇЁv ўшёыр.
+сЁрЄшЄх тэшьрэшх эр эютvх ъюьрэфv фы юяЁхфхыхэш ъюышўхёЄтр ЄръЄют фы тvяюыэхэш ЇєэъЎшщ. ¦юьрэфр RDTSC (Read Time-stamp counter) - тvяюыэ хЄё фы яЁюЎхёёюЁют, эрўшэр ё PENTIUM MMX ш тючтЁр•рхЄ т ЁхушёЄЁх EAX
+Єюсv юяЁхфхышЄ№, яюффхЁцштрхЄё ыш фрээр ъюьрэфр фы яЁюЎхёёюЁр, шёяюы№чєхЄё ъюьрэфр CPUIDЧCPU Identification
¦Єр ъюьрэфр Єръцх яючтюы хЄ юяЁхфхышЄ№ яЁюшчтюфшЄхы яЁюЎхёёюЁр, ёхьхщёЄтю, ъюЄюЁюьє юэ яЁшэрфыхцшЄ, ьюфхы ш тхЁёшш, р Єръцх фЁєує¦ шэЇюЁьрЎш¦. -ы юяЁхфхыхэш Єшяр тvфртрхьющ шэЇюЁьрЎшш шёяюы№чєхЄё ЁхушёЄЁ EAX ( Єрсы 11.3). ¦юьрэфр CPUID ьюцхЄ тvяюыэ Є№ё фы ы¦сюую єЁютэ яЁштшыхушщ.
TрсышЎр 11.3
|
Tїюфэvх фрээvх |
Tvїюфэvх фрээvх |
|
EAX = 0 |
EAX Ц ьръёшьры№эюх чэрўхэшх, ъюЄюЁюх ьюцэю чрфртрЄ№ эр тїюфх (юсvўэю 2) EBX, EDX, ECX Ц яЁюшчтюфшЄхы№ Ц -ы PENTIUM ёЄЁюър лGenuineIntel¬ ЁрёяЁхфхыхэр Єръ: EBXа ?756e6547h (* "Genu", сєътр G т BL *) EDXа ?49656e69h (* "ineI", сєътр i т -DL *) ECXа ?6c65746eh (* "ntel", сєътра n т CL *) |
|
EAX = 1 |
EAX Ц тхЁёш яЁюЎхёёюЁр (Єшя, ёхьхщёЄтю, ьюфхы№, step) EBX - ЁхчхЁт ECX- ЁхчхЁт EDX Ц шэЇюЁьрЎш юс юёюсхээюёЄ ї яЁюЎхёёюЁр |
|
EAX = 2 |
EAX ЦшэЇюЁьрЎш ю ъ¤°х EBX -ЦшэЇюЁьрЎш ю ъ¤°х ECX- ЦшэЇюЁьрЎш ю ъ¤°х EDX Ц ЦшэЇюЁьрЎш ю ъ¤°х |
¦рёёьюЄЁшь ёюфхЁцшьюх Єюы№ъю эхъюЄюЁvї сшЄют ЁхушёЄЁр EDX:
23 Ц яюффхЁцштрхЄё MMX Ц Єхїэюыюуш ;
18 Ц яЁюЎхёёюЁ яюффхЁцштрхЄ 96 сшЄэvщ єэшъры№эvщ эюьхЁ яЁюЎхёёюЁр.
4 Ц яюффхЁцштрхЄё ъюьрэфр RDTSC
¦ЁшьхЁ 4. TюёЄртшЄ№ ЇєэъЎш¦, ъюЄюЁр тючтЁр•рхЄ юЄтхЄ 1, хёыш ъюьрэфр RDTSC яюффхЁцштрхЄё ш 0 т яЁюЄштэюь ёыєўрх.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
resаааааа ddааааааа ?
codeseg
begin:
callааааа IsReadCount
movаааа [res], eax
call ExitProcess
proc ааа IsReadCount
movаааа eax, 1
cpuid
movаааа eax, 1
testаааааа edx, 1 shl 4
jnzааааааа short m1
subаааааа eax, eax
m1:
Retаааааа
Endp
end begin
Передача в списке параметров многомерных массивов
При рассмотрении механизма определения адреса для двухмерного массива было замечено, что для определения адреса текущего элемента массива необходимо знать размер одного элемента
и длину строки (или количество элементов в одной строке). Поэтому в списке параметров необходимо обязательно задавать непосредственно длину строки или параметр, который позволит вычислить эту длину.
Пример 1 . Составить главную программу и функцию для умножения заданного столбца матрицы на заданное значение.
ideal
p686
model flat
extrn ExitProcess:proc
dataseg
matr dd 1, 1, 1, 1
dd 2, 2, 2, 2
dd 3, 3, 3, 3
value dd 4
col dd 1
n dd 4; количество столбцов
m dd 3; количество строк
codeseg
begin:
push offset matr [n] [m] [value] [col]; ebp 0
call mc_mul; eip 4
call ExitProcess; col 8
proc mc_mul; value 12
push ebp; m 16
mov ebp, esp; n 20
; matr 24
;for(i=0; i
push eax ebx ecx esi
mov ecx, [ebp+16]
mov esi, [ebp+24]
mov ebx, [ebp+8]; col
lea esi, [esi+ebx*4]; & начала
mov ebx, [ebp+20]
shl ebx, 2; Размер строки
mov edi, [ebp+12]
for1:
mov eax, [esi]
mul edi
mov [esi], eax
add esi, ebx
loop for1
pop esi ecx ebx eax ebp
Ret 5*4
Endp
end begin
Пример 2.
Для заданного массива строк определить длины строк
ideal
p686
model flat
extrn ExitProcess:proc
dataseg
s1 db 'a', 0
s2 db 'bb', 0
s3 db 'ccc', 0
s4 db 'dddd', 0
array dd s1, s2, s3, s4
n dd (n-array)/4
lenаааааа ddааааааа 4 dup (?)
codeseg
begin:
push ааа offset array [n] offset lenаааааааа
callааааа TextLen
call ExitProcess;
proc ааа TextLen;аа
pushаааа ebp
movаааа ebp, esp;ааааааааааааааааааааааааааааааааа TT+¦
;for (i=0; i
;аа len[i]=j;аааааааааааааааааааааааааа аааааааааааааа lenаааааа 8
;}ааааааааааааааааааааааааааааааааааааа аааааааааааааааааа nааааааааа 12
;аааааааааааааааааааааааааааааааааааааа ааааааааааааааааааа arrayаа 16
pushad
movаааа ecx, [ebp+12]
movаааа eax, [ebp+16]
movаааа edi, [ebp+8]
fori:
subаааааа edx, edx
leaаааааа ebx, [eax+ecx*4-4]
movаааа esi, [ebx]
forj:
movаааа bl, [esi+edx]
testаааааа bl, bl
jeаааааааа short breakj
incаааааа edx
jmp аааа forj
breakj:
movаааа [edi+ecx*4-4], edx
loopаааа fori
popad
popааааа ebp
Retаааааа 3*4ааааа
Endp
end begin
+сЁрЄшЄх тэшьрэшх эр ЁрсюЄє ё рфЁхёюь ьрёёштр рфЁхёют!
¦ЁшьхЁ 3. LяюЁ фюўшЄ№ ьрёёшт ёЄЁюъ т яюЁ фъх єсvтрэш фышэ ёЄЁюъ.
ideal
p686
modelаа flat
extrn ExitProcess:proc
dataseg
s1аааааааа dbааааааа 'a', 0
s2аааааааа dbааааааа 'bb', 0
s3аааааааа dbааааааа 'ccc', 0
s4аааааааа dbааааааа 'dddd', 0
arrayаа ddааааааа s1, s2, s3, s4
n аааааааа ddааааааа (n-array)/4
lenаааааа ddааааааа 4 dup (?)
codeseg
begin:
push ааа offset array [n] offset lenаааааааа
callааааа TextLen
push ааа offset array [n] offset len
callааааа SortLenааааааааааа
call ExitProcess;
proc ааа TextLen;аа
pushаааа ebp;ааааааааааааааааааааааааааааааааааа
movаааа ebp, esp;ааааааааааааааааааааааааааа ааа
;for (i=0; i
;аа len[i]=j;аааааааааааааааааааааааааа lenааааааааа 8
;}ааааааааааааааааааааааааааааааааааааа nаааа 12
;аааааааааааааааааааааааааааааааааааааа arrayаааааааааа 16
pushad
movаааа ecx, [ebp+12]
movаааа eax, [ebp+16]
movаааа edi, [ebp+8]
fori:
subаааааа edx, edx
leaаааааа ebx, [eax+ecx*4-4]
movаааа esi, [ebx]
forj:
movаааа bl, [esi+edx]
testаааааа bl, bl
jeаааааааа short breakj
incаааааа edx
jmp аааа forj
breakj:
movаааа [edi+ecx*4-4], edx
loopаааа fori
popad
popааааа ebp
Retаааааа 3*4ааааа
Endp
proc SortLen
pushаааа ebp
movаааа ebp, esp
pushad
;for (i=1; i
;аа for (j=i-1; j>=0; j--)аааааааааааааааааааааааааааааааааааааааааааааа arrayаа 8ааааааааа
;аааааааааа if (x[j]
;}
movаааа eax, 1; i
movаааа ebx, [ebp+8]; array
movаааа edx, [ebp+16]; len
@@fori:
pushаааа ebp
movаааа esi, [ebx+eax*4]; r1
movаааа edi, [edx+eax*4]; r2
movаааа ecx, eax; j
@@forj:
decаааааа ecx
jsааааааааа short @@breakj
cmpаааа [edx+ecx*4], edi
jaeаааааа short @@breakj
movаааа ebp, [edx+ecx*4]
movаааа [edx+ecx*4+4], ebp
movаааа ebp, [ebx+ecx*4]
movаааа [ebx+ecx*4+4], ebp
jmp @@forj
@@breakj:
movаааа [ebx+ecx*4+4], esi
movаааа [edx+ecx*4+4], edi
popааааа ebp
incаааааа eax
cmpаааа eax, [ebp+12]
jbeаааааа @@foriааа аааааа ааа
popad
popааааа ebp
retааааааа 12
endp
end begin
+сЁрЄшЄх тэшьрэшх эр шёяюы№чютрэшх ьхЄюъ тшфр @@шь . ¦Єю Єръ эрчvтрхьvх ыюъры№эvх ьхЄъш, юэш шёяюы№чє¦Єё фы юуЁрэшўхэш юсырёЄш фхщёЄтш ьхЄюъ. +сырёЄ№ фхщёЄтш ьхЄъш Ц ЇєэъЎш шыш юсырёЄ№ ьхцфє фтєь юсvўэvьш ьхЄърьш.
¦Ёш ёюЁЄшЁютъх ьхэ ¦Єё ьхёЄрьш эх ёрьш ёЄЁюъш, р шї рфЁхёр ш фышэv. ¦Єю ьюцхЄ сvЄ№ чэрўшЄхы№эю ¤ЇЇхъЄштэхщ юсьхэр ьхёЄрьш ёЄЁюъ, хёыш юэш фышээvх.
а
Tръ ъръ яЁшэЎшяv ЁрсюЄv ё фтєїьхЁэvьш ш ьэюуюьхЁэvьш ьрёёштрьш юфшэръютv, ёюёЄртыхэшх ЇєэъЎшщ фы ьэюуюьхЁэvї ьрёёштют тvяюыэ хЄё яю Єхь цх яЁртшырь, ўЄю фы фтєїьхЁэюую ьрёёштра
Передача в списке параметров структур
Пример 1. Составить главную программу и функцию для вычисления суммы двух комплексных чисел.
p686
IDEAL
MODEL flat
extrn ExitProcess:proc
DATASEG
struc complex
real dd ?
im dd ?
ends
n1 complex <1, 1>
n2 complex <2, 2>
n3 complex ?
CODESEG
begin:
push [n1.im]
push [n1.real]
push [n2.im]
push [n2.real]
push offset n3
call csum
call ExitProcess
proc csum
push ebp
mov ebp, esp
push eax ebx
mov eax, [(complex ebp+20).real]
add eax, [(complex ebp+12).real]
mov ebx, [ebp+8]
mov [(complex ebx).real], eax
mov eax, [(complex ebp+20).im]
add eax, [(complex ebp+12).im]
mov [(complex ebx).im], eax
pop ebx eax ebp
ret 20
endp csum
end begin
Обратите внимание на запись компонентов комплексного числа в стек. Это число записывается так, чтобы младшая часть числа находилась в младших адресах стека.
Аналогично со структурами используются объединения.
Передача в списке параметров функций
Функции передаются, если необходимо решить класс задач, которые различаются только списком используемых функций, причем эти функции имеют одинаковый список параметров. Пример – вычисление определенного интеграла для разных подинтегральных функций.
Для функций передаются их адреса. Для обращения к функции, заданной своим адресом, используется косвенный вызов.
Пример . Составить главную программу и функции вычисления:
Min – для вычисления минимального из двух заданных целых чисел;
Max – для вычисления максимального из двух заданных целых чисел;
MinMax - для вычисления минимального или максимального из двух заданных целых чисел, в зависимости от заданной в списке параметров функции;
ideal
p686
model flat
extrn ExitProcess:proc
dataseg
x dd 5
y dd 3
z1 dd ?; min
z2 dd ?; max
array dd Min, Max
codeseg
begin:
push [x] [y]
mov eax, [array]; min
push eax
call MinMax
mov [z1], eax
push [x] [y]
mov eax, [array+4]; min
push eax
call MinMax
mov [z2], eax
call ExitProcess; ebp 0
proc Min; eip 4
push ebp; y 8
mov ebp, esp; x 12
mov eax, [ebp+12]
cmp eax, [ebp+8]
jl short @@m1
mov eax, [ebp+8]
@@m1:
pop ebp
ret 8
endp
; ebp 0
proc Max; eip 4
push ebp; y 8
mov ebp, esp; x 12
mov eax, [ebp+12]
cmp eax, [ebp+8]
jg short @@m1
mov eax, [ebp+8]
@@m1:
pop ebp
ret 8
endp
; ebp 0
proc MinMax; eip 4
push ebp; &fun 8
mov ebp, esp;
; y 12
push [dword ebp+16] [dword ebp+12]; x 16
mov eax, [ebp+8]
call eax
pop ebp
ret 12
endp
end begin
Классификация параметров. Способы передачи параметров процедурам
Параметры делятся на параметры исходные данные и параметры результаты. Для параметров первого типа задаются их значения, для параметров второго - адреса. Для передачи параметров используются регистры, стек или другая общая для вызывающей и вызываемой программ область памяти.
Составление функций с переменным списком параметров
Пример. Составить главную программу и функцию для вычисления максимального числа для одного, двух и более чисел.
p586
IDEAL
MODEL fLat
extrn ExitProcess:proc
DATASEG
x dd 1
y dd 2
z dd 3
u dd 4
res dd ?
CODESEG
begin:
push [u] [z] [y] [x] 4; ebp 0
call max; eip 4
add esp, 5*4; 4 8 ; Постоянные
mov [res], eax; [x] 12
call ExitProcess; [y] 16; Переменные
proc max; [z] 20
push ebp; [u] 24
mov ebp, esp;
push ebx ecx edx
mov eax, [ebp+12]; x
mov ecx, [ebp+8]; n
dec ecx
jecxz @@m2
lea ebx, [ebp+16]; & переменной части списка
@@for1:
mov edx, [ebx]
cmp eax, edx
jge short @@m1
mov eax, edx
@@m1:
add ebx, 4
loop @@for1
@@m2:
pop edx ecx ebx ebp
ret
endp
end begin
Обратите внимание на:
1. Формирование адреса начала переменной части списка;
2. Порядок передачи параметров в стек – начинаем с переменных параметров, чтобы постоянные параметры были записаны с постоянным смещением в вершине стека;
3. очистку стека, которую выполняет вызывающая программа, т.к. только она «знает», сколько передала параметров;
Составление рекурсивных функций на ассемблере
Функция называется рекурсивной, если она обращается к самой себе. Рассмотрим составление рекурсивной функции на примере вычисления факториала.
Пример. Составить функцию вычисления Y = N!,
если
Y = 1, если N = 0,
Y = 1 * 2 ... * N, если N > 1
Если N < 0 функция должна возвращать -1, что говорит о неправильном аргументе.
Соответствующая функция на языке С имеет вид:
unsigned fact(int n){
if(n < 0) return -1;
if(n == 0)return 1
return (n * fact(n - 1));
}
;Использование рекурсий в ассемблер программах
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
n dd 4
x dd ?
codeseg
proc fact
push ebp
mov ebp,esp; [ebp+8]-n
push ebx ecx edx
mov ebx, [ebp+8]
test ebx,ebx
js m1; <0
jz m2; ==0
mov ecx, ebx
dec ebx
push ebx
call fact
mul ecx
jmp short m3
m1: mov eax,0
jmp short m3
m2: mov eax,1
m3: pop edx ecx ebx
pop ebp
ret 4
endp fact
begin:
push [n]
call fact
call ExitProcess
end begin
"Прокрутите" программу, чтобы увидеть, как изменяется состояние стека. Вы увидите, что рекурсивные функции требуют, чтобы размер стека был не меньше K * N, где K-число байт стека, используемых при каждом вызове, а N - число вызовов рекурсивной функции
Одномодульные программы
Одномодульная программа может состоять из главной программы (все рассмотренные до сих пор примеры) или из главной программы и процедур (функций).
Использование общих областей памяти
При составлении многомодульных программ программисты часто используют внешние переменные, что позволяет один раз выделить память под переменную и один раз ее инициализировать. Такие переменные определяются в сегменте данных одного из модулей и задаются в директиве PUBLIC этого модуля, а во всех модулях, где используются эти переменные, они определяются в директиве EXTRN.
Пример. Составить функцию вычисления суммы двух чисел, передавая исходные данные через внешние переменные.
; главная программа
ideal
p686
model flat
extrn ExitProcess:proc
extrn Summa:proc
dataseg
y dd 3
x dd 5
z dd ?
public x
public y
codeseg
begin:
call Summa
mov [z], eax
call ExitProcess
end begin
; Функция
ideal
p686
model flat
extrn x:dword, y:dword
codeseg
proc Summa
public Summa;
mov eax, [x]
add eax, [y]
ret
endp
end
Особенности использования внешних функций
Внешняя функция транслируется отдельно от вызывающей программы. Это позволяет не транслировать каждый раз модули, в которых не было изменений. Упрощается изолированная от всей системы отладка отдельных модулей, что допускает участие в разработке системы группы программистов. Не следует заботиться о локальных именах программы, например метках, которые могут совпадать для различных модулей. И, наконец, появляется возможность стыковки разноязыковых модулей.
Чтобы объединить несколько модулей (эту функцию выполняет компоновщик) в одну программу, необходима специальная информация для вызывающей и вызываемой программ. Рассмотрим эту информацию.
В Ы З Ы В А Ю Щ А Я П Р О Г Р А М М А .
1. Необходимо знать, что вызываемый модуль является внешним. Для задания этого этого используется директива:
extrn имя функции : proc
.Директива записывается вне сегментов, в этом случае компоновщик будет искать определение этого имени во всех сегментах, пока не найдет.
2. Если вызывающая программа резервирует память под данные, которые должны использоваться в функции, адреса этих данных должны быть определены как адреса типа public, т.е. доступные(известные) другим сегментам.
Общий вид директивы public:
public имя1, имя2, ....
Директива public, если она необходима, задается в том сегменте, где определено это имя. Директиву можно использовать не только для переменных, но и для меток.
В Ы З Ы В А Е М А Я П Р О Г Р А М М А
1. Имя функции должно быть задано в директиве public
public имя_ функции
2. Если функция использует данные, память под которые выделена в другой программе, то в этой программе используется директива
extrn определение1, определение2,...
Общий вид определения для передаваемых данных:
имя : тип : количество ,
где
имя - имя данного, память под которое выделена в другом модуле;
аааааааааааааааа ; ¦хрышчрЎш рыуюЁшЄьр
аааааааааааааааа ; TюёёЄрэютыхэшх шёяюы№чєхьvї ЁхушёЄЁют
аааааааааааааааааааааааа .........
ааааааааааааааааааааааааааа ret
ааааааааааааааааа шь а endp
аааааааааааааааааааааа end
аааа T ЇєэъЎшш, т юЄышўшх юЄ уыртэющ яЁюуЁрььv, т фшЁхъЄштх end рфЁхё Єюўъш тїюфр эх чрфрхЄё .
аааа +с•шщ тшф тvчvтр¦•хщ яЁюуЁрььv :
аааааааааааааааа .MODELа шь
ааааааааааааааааааааааа ......
аааааааааааааааа extrnа шь _яЁюЎ : proc
ааааааааааааааааааааааа ......
аааааааааааааааа .CODE
аааааааа begin:
аааааааааааааааааааааааа .......
ааааааааааааааааа endа begin
ааа -шЁхъЄштv public ш extrn т ¤Єшї яЁюуЁрььрї ьюцэю чрьхэшЄ№ фшЁхъЄштющ global шь _яЁюЎ:proc. =ю ьv тёх-Єръш Ёхъюьхэфєхь трь шёяюы№чютрЄ№ фшЁхъЄштv public ш extrn,а Є.ъ.а юэша яючтюы ¦Єа сюыхх уыєсюъю шчєўшЄ№ ьхїрэшчь ёт чш ьюфєыхщ. Lьхээю ¤Єш фшЁхъЄштv эршсюыхх ўрёЄю шёяюы№чє¦Єё т юяєсышъютрээvї яЁюуЁрььрї. ¦юёых Єюую, ъръ тv єтхЁхээю яюўєтёЄтєхЄх ёхс т шёяюы№чютрэшш ¤Єшїа фшЁхъЄшт, ьюцэю шї чрьхэшЄ№ фшЁхъЄштющ global.
аааа -ы ёючфрэш шёяюыэ хьющ яЁюуЁрььvа шча эхёъюы№ъшїа ьюфєыхщ, тvяюыэ хЄё Ёрчфхы№эр ЄЁрэёы Ўш фы ърцфюую ьюфєы :
ааааааааааааааааа Tasm32 ./ml /ziа шь _ьюфєы 1
ааааааааааааааааа Tasm32 /ml /ziа шь _ьюфєы 2
ааааааааааааааааа ...
ш ъюьяюэютър тёхї ьюфєыхщ тьхёЄх:
ааааааааааааааааа tlink32 /v шь _ьюфєы 1 шь _ьюфєы 2 ....import32.lib
аааа -ы ЇюЁьшЁютрэш юЄырфюўэющ шэЇюЁьрЎшша шёяюы№чє¦Єё а Єха цх ъы¦ўш, ўЄю ш фы юфэюьюфєы№эvї яЁюуЁрьь.
аааа ¦ЁшьхЁv ёюёЄртыхэш ш шёяюы№чютрэш тэх°эшїа ЇєэъЎшщ.
аааа ¦ЁшьхЁ 1. TюёЄртшЄ№ тэх°э¦¦ ЇєэъЎш¦ фы тvўшёыхэш а z=x+y.
¦рЁрьхЄЁv яхЁхфртрЄ№ ўхЁхч ёЄхъ.
;Tэх°э ЇєэъЎш фы тvўшёыхэш z=x+y. ¦рЁрьхЄЁv
;ааааааааааааааа яхЁхфр¦Єё ўхЁхч ёЄхъ.
;+ыртэр яЁюуЁрььр
; Їрщы main.asm
ideal
p686
model flat
extrn ExitProcess:proc
extrn Summa:proc
dataseg
xааааааааа ddааааааа 5
yааааааааа ddааааааа 3
zаааааааааа ddааааааа ?
codeseg
begin:
pushаааа [x] [y]
callааааа Summa
movаааа [z], eax
call ExitProcess
end begin
; Lрщы PROC.asm
ideal
p586
modelаа flat
codeseg
procа _Summa
public _Summa
arg x:dword, y:dword
pushаааа ebp
movаааа ebp, esp
MOVаа EAX, [x]
addааааа eax, [y]
popааааа ebp
retааааааа
endp
end
аааа -ы ЄЁрэёы Ўшш ¤Єшї Їрщыют шёяюы№чє¦Єё ъюьрэфv
аааааааааааааааааааааааа Tasm32 /zi /mlа main
аааааааааааааааааааааааа Tasm32 /zi /mlа proc
аааааааааааааааааааааааа Tlink32 /vа main proc import32.lib,
т Ёхчєы№ЄрЄх тvяюыэхэш ъюЄюЁvї яюыєўшь Їрщы main.exe.
Реентерабельное программирование
При обычном использовании общих областей проблем не возникает, так как большинство программ выполняется последовательно, одна за другой. Но, что произойдет, если попытаться выполнить одну и ту же функцию более одного раза и в одно и то же время?
Когда это может произойти?
По крайней мере, это возможно в трех случаях. Во-первых, мультизадачные системы могут иметь множество выполняемых программ, разделяющих общие библиотеки программ, (dll). Вместо наличия нескольких копий этих библиотек имеется только одна копия. Для правильной работы библиотеки должны иметь отдельные области данных, чтобы избежать неумышленное совместное использование и порчу данных. Второй случай, когда одна и та же функция может быть вызвана программами одновременно, происходит в системах управления прерываниями. Допустим, что выполняется некоторая программа и произошло прерывание из-за некоторого внешнего события. Программа, обслуживающая прерывание, начинает выполнение и ей необходимо вызвать программу, которая была прервана. Если она не имеет отдельной области данных, то программа обслуживания прерывания разрушит данные, относящиеся к прерванной программе. По этой причине программам обслуживания прерываний необходимо иметь отдельные области данных.
Третий случай использования отдельных областей данных происходит тогда, когда программе необходимо вызвать саму себя. (рекурсия).
Для написания повторно входимых (реентерабельных) программ необходимо, чтобы все промежуточные данные записывались в локальные области памяти. Этому требованию должны удовлетворять все библиотеки, которые могут использоваться в многопоточном режиме.
Использование локальных областей памяти
Для выделения локальной области используется стек.
Структура стека с локальной областью:
; Сохранение регистров
; Локальная область памяти
EBP
EIP
; Фактические параметры
Для выделения локальной области используется команда:
SUB ESP, Размер локальной области
Для освобождения локальной области используется 2 способа:
Последний способ более защищен от ошибок, связанных с асинхронным использованием стека
Структура функции с локальной областью:
Proc Имя
Push EBP
Mov EBP, ESP
SUB ESP, Размер локальной области
PUSH Регистры
…
POP Регистры
MOV ESP, EBP
POP EBP
RET [Константа]
Пример. Составить главную программу и функцию для преобразования числа в символьное представление.
Алгоритм.
void itoa (unsigned x){
int numbers[] = {10000, 1000, 100, 10, 1};
for (int I=0; I<5; I++){
Y[I]= x/numbers[i] + ‘0’;
X%= numbers[i];
}
}
ideal
p586
model flat
codeseg
proc itoa; ebp 0
public itoa; eip 4
;; y 8
;; x 12
push ebp
mov ebp, esp
sub esp, 5*4
push eax ebx ecx edx esi
mov [numbers], 10000
mov [numbers+4], 1000
mov [numbers+8], 100
mov [numbers+12], 10
mov [numbers+16], 1
mov ecx, 5
mov eax, [ebp+12]
mov ebx, [ebp+8]
lea esi, [numbers]
for1:
xor edx, edx
div [dword ptr esi]
add al, '0'
mov [ebx], al
inc ebx
add esi, 4
mov eax, edx
loop for1
mov [byte ptr ebx], 0
pop esi edx ecx ebx eax
mov esp, ebp
pop ebp
ret 8
endp itoa
end
Составьте самостоятельно главную программу для этого примера и проверьте ее на машине!
Особенности использования команд ENTER и LEAVE
Анализ функций, составленных ранее, показывает, что в функции требуется:
Сохранить и сформироваить значение регистра EBP;
Выделить локальную память.
Для обеспечения этих операций можно использовать команду ENTER (вход), а для автоматического освобождения локальной области и восстановления регистра EBP – команду LEAVE (выход).
Общий вид команды ENTER:
ENTER оп1, оп2
Оп1- определяет размер локальной области в байтах (константное выражение);
Оп2 – уровень вложенности функций (Константное выражение 0 .. 31). Для каждого уровня вложенности копируется фрейм стека при создании нового фрейма. Это делается для обеспечения возможности доступа к параметрам и локальной области внешних функций.
Команда ENTER , если она используется, является первой командой функции, а команда LEAVE – непосредственно перед командой RET.
Если уровень вложенности равен 0, то вставляются команды:
PUSH EBP
MOV EBP, ESP
SUB ESP, оп1.
Для уровня вложенности один или больше, процессор сохраняет указатели на фреймы стеков для предыдущих уровней (содержимое регистра EBP для всех предыдущих уровней).
Команда LEAVE выполняет действия, обратные действиям ENTER, в том числе восстановление регистра EBP.
Директива ARG
Используется для задания имен и типов формальных параметров. Параметры записываются, начиная с вершины стека. Общий вид директивы:
ARG параметр1, параметр2, … [= переменная]
Параметры задаются в виде:
Имя:Тип[:Количество]
В качестве имени используется любое уникальное имя.
Тип может быть стандартным и нестандартным. В качестве стандартных используются типы: BYTE, WORD, DWORD, PWORD, FWORD, QWORD, TBYTE. В качестве нестандартных можно задавать типы структур и объединений.
Количество задается константным выражением и определяет количество элементов данного типа. Используется, если передается массив, а не его адрес. По умолчанию Количество
= 1. Переменной, заданной в директиве присваивается размер области параметров в стеке. Это значение вычисляется компилятором и используется в команде RET.
Пример. Составить функцию для вычисления значения Y=A*X+B для данных длиной 32 бита. Результат длиной 4 байта
; Главная программа
ideal
p686
model flat
extrn ExitProcess:proc
extrn Fun:proc
dataseg
a dd 3
x dd 5
b dd 7
y dd ?
codeseg
begin:
push [a] [x] [b]
call Fun
mov [y], eax
call ExitProcess
end begin
; Функция
ideal
p686
model flat
codeseg
proc Fun
public Fun
arg b:dword, x:dword, a:dword = z
push ebp
mov ebp, esp
mov eax, [a]
mul [x]
add eax, [b]
pop ebp
ret z
endp
end
Директива LOCAL
Директива используется для определения структуры локальной области и ее размера. Сама локальная область не создается. Для ее создания необходима команда SUB ESP, Размер или команда ENTER.
Общий вид директивы:
LOCAL Параметр1, Параметр2,… = Переменная
Локальные параметры задаются точно также, как формальные параметры, т.е. задается имя, тип и, возможно, количество. Размер локальной области присваивается на этапе компиляции переменной, заданной в конце директивы . Переменная используется в командах выделения локальной области.
Пример. Составить главную программу и функцию на ассемблере
int Fun (int x){
int a, b[2];
a=x; b[0]=b[1] = 2 *x;
return a+ b[0] + b[1];
}
; Главная программа
;Int Fun (int x){
; Int a, b[2];
; a=x; b[0]=b[1] = 2 *x;
; return a+ b[0] + b[1];
;}
ideal
p686
model flat
extrn ExitProcess:proc
extrn Fun:proc
dataseg
x dd 5
y dd ?
codeseg
begin:
push [x]
call Fun
mov [y], eax
call ExitProcess
end begin
; Функция
;Int Fun (int x){
; Int a, b[2];
; a=x; b[0]=b[1] = 2 *x;
; return a+ b[0] + b[1];
;}
ideal
p686
model flat
codeseg
begin:
proc Fun
public Fun
arg x:dword = z1
local a:dword, b:dword:2=z2
push ebp
mov ebp, esp
sub esp, z2
mov eax, [x]
mov [a], eax
mov [b], eax
add [b], eax
mov [b+4], eax
add [b+4], eax
mov esp, ebp
pop ebp
ret z1
endp
end
Вставка в Си команд на ассемблере
Рекомендации и правила записи вставок.
1. Команда должна начинаться с ключевого слова asm. При использовании С++
несколько подряд идущих команд могут быть объединены в блок, например
asm {
.......
}
Некоторые версии трансляторов с языка СИ требуют слово asm перед каждой командой или вместо этого слова используют _asm или др.
2. Вставлять можно все допустимые машинные команды и директивы, не определяющие работу транслятора. Так не допускаются директивы
segment и ends ;
proc и endp;
assume и end
и др.
3. В ассемблерной части программы допускается использование данных Си программы всех классов (локальные, внешние, статические и регистровые). Данные могут быть объявлены в ассемблерной части программы, как внешние переменные для использования в ассемблерной части программы. Эти переменные операторы языка «С» не видят, например:
#include
int y;
asm {
x dd ?
}
int main(int argc, char* argv[])
{
asm{
mov [x], 5
mov eax,[x]
mov [y],eax
}
cout << y;
char cc;
cin >> cc;
return 0;
}
Здесь показано как использовать данные, определенные в участках программы на С и на ассемблере.
4. Для некоторых версий языка С[15], если в ассемблерной части есть команда перехода, то метка записывается по правилам Си, например:
asm jmp label
............
label:asm xor ax, ax
5. Комментарии записывают по правилам Си. Символ ';' используется в качестве конца команды, а не начала комментария. В строке может быть записано несколько команд, например :
asm {push ax; push bx; push cx}
После последней или единственной команды символ ';' можно не ставить. Команды должны быть написаны по правилам MASM, не IDEAL.
6. В ассемблерной части программы нельзя изменить содержимое сегментных регистров, регистров для работы со стеком (esp, ebp), а также регистров esi, edi, если используются регистровые переменные[16].
7. Если встречается хотя бы одна ассемблерная вставка, необходим дополнительный шаг трансляции с языка Си на ассемблер. Для задания транслятору режима перевода в ассемблерный код используется директива
#pragma inline
Если директивы нет, возможна повторная трансляция, если встречена команда на ассемблере.
Ниже приведен пример программы с ассемблерными вставками
#pragma inline
#include
int summa (int x, int y){
int z;
asm {
mov eax, [x]
add eax, [y]
mov [z], eax
}
return z;
}
main(){
printf(" сумма = % d\n", summa(5, 3));
}
Достоинство метода: наиболее прост с точки зрения программиста.
Недостаток метода. Нельзя отдельно оттранслировать ассемблерную часть.
Использование отдельных функций на ассемблере
Функция должна удовлетворять соглашениям по вызову для языка С++.
Соглашения по вызову различаются по следующим параметрам:
Правила формирования внутреннего имени функции;
Правилами передачи параметров;
Правилами очистки стека.
При формировании имени возможен регистро – чувствительный или регистро нечувствительный режим, возможно формирование дополнительных символов для обеспечения свойства перегрузки функции.
Для обеспечения возможности использования переменного списка параметров параметры могут записываться в стек, начиная с конца списка, для обеспечения максимальной скорости работы – для передачи параметров используются регистры
Очистку стека может выполнять вызывающая программа или функция.
В табл. 11.4 представлены соглашения по вызову и свойства этих соглашений.
Если файл с программой имеет расширение CPP, то по умолчанию используется режим 1, если файл с расширением С++ - режим 2. Если в программе с расширением СPP необходимо использовать функцию с соглашением С, заголовок этой функции имеет вид:
Extern “C” заголовок;
Если необходимо задать заголовочный файл, который можно использовать в программе на С и СРР, то применяют директивы
#ifdef __cplusplus
extern “C” {
#endif
заголовок 1;
заголовок 2;
…
#ifdef __cplusplus
}
#endif
Пример. Составить вызывающую программу и функции вычисления разности для двух чисел, используя все возможные соглашения по вызову.
Таблица 11.4. Соглашения по вызову, принятые в С++
| № п/п | Тип соглашения | Как задается | Внутреннее имя функции | Порядок передачи параметров | Очистка стека | Переменный список параметров | |||||||
| 1. | Cpp | - | @имя$q[17]… | ¬ | Вызывающая программа | Да | |||||||
| 2. | С | Extern “C”… | _имя | ¬ | Вызывающая программа | Да | |||||||
| 3. | Fastcall | Тип __fastcall … | @имя$qqr… | EAX, EDX, ECX | Вызывающая программа | Нет | |||||||
| 4. | Stdcall | Тип __stdcall … | @имя$qqs… | ¬ | Функция | Нет | |||||||
| 5. | Pascal | Тип __pascal … | @ИМЯ$Q… | ® | Функция | Нет |
// +ыртэр яЁюуЁрььр
#pragma hdrstop
#include
#include
//---------------------------------------------------------------------------
USEASM("std.asm");
USEASM("std1.asm");
USEASM("std2.asm");
USEASM("std3.asm");
USEASM("std4.asm");
//---------------------------------------------------------------------------
#pragma argsused
int __stdcallа substract ( int x, int y, int *z);
intаа substract1 (int x, int y, int *z);
intаа __fastcall substract2 (int x, int y, int *z);
extern "C" intаа substract3 (int x, int y, int *z);
intаа pascal substract4 (int x, int y, int *z);
intаа __fastcall sum (int *x, int y);
int main(int argc, char **argv)
{
ааааа int z;
ааааа substract (3, 2, &z); printf ("%d\n", z);
ааааа substract1 (3, 2, &z); printf ("%d\n", z);
ааааа substract2 (3, 2, &z); printf ("%d\n", z);
ааааа substract3 (3, 2, &z); printf ("%d\n", z);
ааааа substract4 (3, 2, &z); printf ("%d\n", z);
ааааа getchar ();
ааааааа return 0;
}
; stdcall
ideal
p586
model flat
codeseg
procааа @substract1$qiipi
public @substract1$qiipi
argаа a:dword, b:dword, c:dword=s
pushааа ebp
movаааа ebp, esp
pushааа ebx
movаааа eax, [a]
subаааа eax, [b]
movаааа ebx, [c]
movаааа [ebx], eax
popаааа ebx ebp
retаааа
endp
end
;fastcall
ideal
p586
model flat
codeseg
procааа @substract2$qqriipi
publicа @substract2$qqriipi
pushааа esi
movаааа esi, eax
subаааа esi, edx
movаааа [ecx], esi
popаааа esi
retаааа
endp
end
а
;extern C
ideal
p586
model flat
codeseg
procааа _substract3
publicа _substract3
argаа a:dword, b:dword, c:dword=s
pushааа ebp
movаааа ebp, esp
pushааа esi
movаааа esi, [a]
subаааа esi, [b]
movаааа ecx, [c]
movаааа [ecx], esi
popаааа esi ebp
ret
endp
end
; pascal
ideal
p586
model flat
codeseg
proc @SUBSTRACT4$QIIPI
public @SUBSTRACT4$QIIPI
argаа c:dword, b:dword, a:dword=s
pushааа ebp
movаааа ebp, esp
pushааа esi
movаааа esi, [a]
subаааа esi, [b]
movаааа ecx, [c]
movаааа [ecx], esi
popаааа esi ebp
retаааа s
endp
end
Стыковка с языками высокого уровня
Существует два способа стыковки:
1. Вставка в Си программу команд на ассемблере;
2. Программа на Си вызывает функцию на ассемблере;
Особенности создания функций для включения их в DLL
Для включения функции в DLL в качестве экспортируемых функций директива PUBLIC должна быть заменена директивой PUBLICDLL.
Пример. Создать экспортируемую функцию для вычитания целых чисел, которая должна входить в DLL как экспортируемая функция. Пусть функция удовлетворяет соглашению по вызову extern “C”:
Файл с функцией proc.asm
;extern C
ideal
p586
model flat
codeseg
proc _substract3
publicdll _substract3
arg a:dword, b:dword, c:dword=s
push ebp
mov ebp, esp
push esi
mov esi, [a]
sub esi, [b]
mov ecx, [c]
mov [ecx], esi
pop esi ebp
ret
endp
end
Главная программа, использующая функцию substract3:
#pragma hdrstop
#pragma argsused
#include
extern "C" __declspec (dllimport) int substract3 (int, int, int *);
int main(int argc, char* argv[])
{
int z;
substract3 (5, 3, &z);
cout << z;
char c;
cin>>c;
return 0;
}
//---------------------------------------------------------------------------
К проекту долженбыть поключен файл с расширением LIB, соответствующий сформированной DLL.
Аналогично можно использовать DLL библиотеку в режиме загрузки с помощью функции Load Library.
Особенности использования ассемблерных функций в качестве функций – членов классов
Пусть задан класс:
class STRING{
char *s;
public:
STRING (char * c){
s = new char[strlen(c)+1];
strcpy (s, c);
}
~STRING (){
delete []s;
}
public:
int len ()
};
Пусть функцию определения длины строки необходимо определить на ассемблере. Определим ее сначала на С. Получим файл:
#pragma inline
#include "mystr.h"
int STRING::len(){
int i;
for (i=0; s[i]; i++);
return i;
}
Здесь директива #pragma inline задана с целью формирования ассемблерного кода. В результате трансляции получим файл на ассемблере, основна часть которого приведена ниже:
.386p
ifdef ??version
if ??version GT 500H
.mmx
endif
endif
model flat
ifndef ??version
?debug macro
endm
endif
?debug S "E:\users\lena\INSTITUT\ASM\USKOR\cl.cpp"
?debug T "E:\users\lena\INSTITUT\ASM\USKOR\cl.cpp"
_TEXT segment dword public use32 'CODE'
_TEXT ends
_DATA segment dword public use32 'DATA'
_DATA ends
_BSS segment dword public use32 'BSS'
_BSS ends
$$BSYMS segment byte public use32 'DEBSYM'
$$BSYMS ends
$$BTYPES segment byte public use32 'DEBTYP'
$$BTYPES ends
$$BNAMES segment byte public use32 'DEBNAM'
$$BNAMES ends
$$BROWSE segment byte public use32 'DEBSYM'
$$BROWSE ends
$$BROWFILE segment byte public use32 'DEBSYM'
$$BROWFILE ends
DGROUP group _BSS,_DATA
_TEXT segment dword public use32 'CODE'
@STRING@len$qv segment virtual
@@STRING@len$qv proc near
?live16385@0:
?debug L 4
push ebp
mov ebp,esp
push ecx
?debug L 6
@1:
xor eax,eax
mov dword ptr [ebp-4],eax
ааааааааааа jmpаааааа short @3
@2:
ааааааааааа incаааааа dword ptr [ebp-4]
@3:
ааааааааааа movаааааа edx,dword ptr [ebp+8]
ааааааааааа movаааааа ecx,dword ptr [edx]
ааааааааааа movаааааа eax,dword ptr [ebp-4]
ааааааааааа cmpаааааа byte ptr [ecx+eax],0
ааааааааааа jneаааааа short @2
ааааааааааа ?debug L 7
ааааааааааа movаааааа eax,dword ptr [ebp-4]
ааааааааааа ?debug L 8
@6:
@5:
ааааааааааа popаааааа ecx
ааааааааааа popаааааа ebp
ааааааааааа ret
ааааааааааа ?debug L 0
@@STRING@len$qv endp
Е
¦ЁюрэрышчшЁєхь яюыєўхээvщ Їрщы. Lч Їрщыр тшфэю, ўЄю шь ЇєэъЎшш Ц ўыхэр ЇюЁьшЁєхЄё шч шьхэш ъырёёр, шьхэш ЇєэъЎшш ш Єшяют ярЁрьхЄЁют. ¦Єю шь ыєў°х тёхую тч Є№ шч юс·хъЄэюую Їрщыр тvчvтр¦•хщ яЁюуЁрььv.
-ы юс·хъЄр ъырёёр яхЁхфрхЄё рфЁхё, уфх эрїюфшЄё рфЁхё эрўрыр ¤Єюую юс·хъЄр, Є.х. яхЁтющ тэєЄЁхээхщ яхЁхьхээющ ¤Єюую ъырёёр (ярЁрьхЄЁ ЇєэъЎшш). -ы фюёЄєяр ъ ёрьющ яхЁхьхээющ шёяюы№чєхЄё фтх ъюьрэфv:
movаааааа edx,dword ptr [ebp+8]
movаааааа ecx,dword ptr [edx]; &s
+•х Ёрч юсЁрЄшЄх тэшьрэшх эр эх¤ЇЇхъЄштэюх шёяюы№чютрэшх ыюъры№эющ яхЁхьхээющ тэєЄЁш ъырёёр!
¦Ёюьх яЁштхфхээvї тv°х ъюьрэф шёяюы№чє¦Єё фюяюыэшЄхы№эvх ъюьрэфv. Lэрышч ъюьрэф яЁюуЁрььv яюърчvтрхЄ, ўЄю яЁю•х яхЁхяшёрЄ№ ъюьрэфv юяЁхфхышЄхы ЇєэъЎшш Ц ўыхэр ъырёёр тэєЄЁш ЇєэъЎшш эр чvъх T++.
¦хрышчрЎш ЇєэъЎшш
#include "mystr.h"
int STRING::len(){
а asm {
аааа movааааааа eax, -1
аааа movааааааа ecx, [ebp+8]
аааа movааааааа ecx, [ecx]
аааа for1:
аааа incаа аааааeax
аааа cmpааааааа byte ptr [ecx+eax], 0
аааа jneа for1
а }
}
TЁртэшЄх ¤ЇЇхъЄштэюёЄ№ ъюфр ё рёёхьсыхЁэющ тёЄртъющ ш схч эхх!
Недостатки функций
Для сравнительной оценки процедур и макросов сначала составим главную программу и функцию для простой задачи, а затем определим недостатки и покажем возможности использования макросов вместо процедур. Определим область применения функций и макросов.
Пример . Составить главную программу и функцию для обмена местами двух целых чисел длиной 32 бита.
; Главная программа
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
first dd 1
second dd 2
codeseg
begin:
push offset first
push offset second
call swap
call ExitProcess
; Функция
proc swap
arg a:dword, b:dword=s
push ebp
mov ebp, esp
push esi edi eax ebx
mov esi, [a]
mov edi, [b]
mov eax, [esi]
mov ebx, [edi]
mov [esi], ebx
mov [edi], eax
pop ebx eax esi edi
pop ebp
ret s
endp swap
end begin
Анализ программы показывает, что для обращения к функции необходимо:
* передать параметры или их адреса (число команд не меньше числа передаваемых параметров);
* обратиться к функции (занесение адреса возврата и передача управления - сброс конвейера и декодирование команд заново - фактически 2 команды + сброс конвейера, что соответствует потере не менее 4 тактов).
В самой функии необходимо:
* обеспечить доступ к параметрам (2 команды);
* восстановить стек и передать управление вызывающей программе (2 команды + сброс конвейера).
Таким образом, суммарные «накладные расходы» вызова функциии составляют N + 6 + 2 сброса конвейера, где N - число параметров. Дополнительные расходы по одной команде для параметров-результатов, т.к. необходимо использовать косвенные адреса. В данной программе исполняемая часть функции содержит всего 4 команды и использовать функцию с 10 (N = 2) дополнительными командами и 2 сбросами процессора очень плохо как с точки зрения времени выполнения, так и с точки зрения памяти! Лучше вставить требуемые команды для обмена прямо в программу.
Таким образом, если число команд функции невелико, или если функция используется однократно, лучше функцию не применять!
Для обеспечения вставки в требуемое место участка программы с настройкой с учетом списка параметров используются макросы
Простейшие макросы
Простейшие макросы используют директиву EQU для определения макроса. Директива используется для задания обозначений операторов или их частей. Такие макросы называются текстовыми.
Пример. Пусть необходимо в программе использовать много диагностических сообщений, которые содержат общие части, например:
msg1 db ‘Ошибка в записи идентификатора’, 13, 10, 0
msg2 db ‘Ошибка в записи числа’, 13, 10, 0
···
msgN db ‘Ошибка в записи выражения’, 13, 10, 0
Для упрощения записи обозначим общие части сообщений m1(Ошибка в записи) и m2(13, 10, 0). Для этого используются операторы:
m1 EQU < Ошибка в записи >
m2 EQU <13, 10, 0>
Угловые скобки в записи означают, что данная запись рассматривается как единое целое, а не состоит из нескольких элементов.
Тогда для задания msg1, msg2,... msgN требуются операторы:
msg1 db m1, ‘ идентификатора’, m2
msg2 db m1, ‘ числа’, m2
···
msgN db m1, ‘ выражения’, m2
Для проверки правильности составленных макросов составим программу для формирования строк:
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
m1 EQU < 'Ошибка в записи' >
m2 EQU <13, 10, 0>
msg1 db m1, ' идентификатора', m2
msg2 db m1, ' числа', m2
msg3 db m1, ' выражения', m2
codeseg
begin:
call ExitProcess
end begin
Для просмотра сформированных строк войдите в режим DUMP и, используя CTRL/G, задайте просмотр области памяти, начиная с msg1. Вы увидите сформированные строки.
Основные определения
Макроопределение - последовательность строк, которая может содержать формальные параметры, причем после замены формальных параметров фактическими получается синтаксически верный участок программы (аналог функции).
Макрокоманда - используется для указания места в программе, куда подставляется группа команд, и фактических параметров. (аналог команды CALL).
Макрорасширение - это макроопределение, которое получается после замены формальных параметров фактическими, т.е. это последовательность команд, которая подставляется в программу.
Макроопределение (м/о)
Общий вид (м/о):
MACRO <Имя> [<Формальный параметр1>[, <Формальный параметр2>[, ...< Формальный параметрN>]]]
Команда1
Команда2
...
КомандаM
ENDM
В режиме MASM <Имя>
задается перед ключевым словом MACRO.
Пример м/о для обмена местами данных:
MACRO swap a,b
push eax, ebx
mov eax, a
mov ebx, b
mov a, ebx
mov b, eax
pop ebx eax
ENDM
М/о может быть записано в любом месте программы до использования
Макрокоманда (м/к)
Общий вид макрокоманды:
<Имя м/о> [<Фактический параметр1>[, < Фактический параметр2>[, ... < Фактический параметрN>]]]
количество формальных параметров, как правило, должно совпадать с числом фактических. Формальных параметров может быть больше, чем фактических (см. ниже).
Пример м/к для м/о SWAP:
SWAP [first], [second]
М/к должна быть записана в том месте программы, где требуется использование м/о.
Макрорасширение (м/р)
Для получения расширения необходимо в м/о подставить фактические команды макрорасширения. Фактические параметры м/к должны быть записаны так, чтобы получились синтаксически правильные команды. Макрорасширение формируется компилятором и подставляется в программу вместо м/к. В листинге программы можно увидеть команды макрорасширения.
Пример. Составить программу для обмена местами данных длиной 32 бита с помощью макросов.
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
first dd 1
second dd 2
codeseg
begin:
MACRO SWAP a, b
push eax ebx
mov eax, a
mov ebx, b
mov a, ebx
mov b, eax
pop ebx eax
endm
SWAP [first], [second]
call ExitProcess
end begin
Откомпилируйте программу в режиме формирования листинга (ключ l). По листингу проанализируйте число сформированных команд для функции и макросов. Сделайте выводы по использованию макросов и функций.
Макросы
Макросы позволяют при трансляции программы заменить одну строку другой или сделать более сложные подстановки с учетом передаваемых параметров. Возможно задание условий трансляции и нетрансляции участка программы и даже повторной трансляции одного и того же участка программы
Правила записи параметров
Ранее составлен макрос для обмена местами двойных слов. Если необходимо менять местами слова, будем использовать другой макрос:
MACRO SWAPW a, b
push ax bx
mov ax, a
mov bx, b
mov a, bx
mov b, ax
pop bx ax
endm
Если бы использовались функции, для каждого типа данных обязательно составлять новую функцию. Для макросов эти макроопределения могут быть объединены в одно, для этого необходимо научиться соединять параметры с постоянной частью или другими параметрами. Например, составляется м/о для работы с байтами или словами. В этом случае используются регистры ax или al cоответственно. Для задания этих регистров буква a используется как постоянная часть, а буквы x
или l являются параметрами.
Для объединения параметров с постоянной частью или другими параметрами используется знак &, т.е. запись имеет вид:
Параметр & Постоянная часть;
Постоянная часть & Параметр
Параметр & Параметр,
например a&Reg, где:
a -Постоянная часть;
Reg -Параметр.
Составим м/о для обмена местами данных для целых длиной 2 и 4 байта.
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
firstd dd 1
secondd dd 2
firstw dw 1
secondw dw 2
codeseg
begin:
MACRO SWAP a, b, type
push type&ax type&bx
mov type&ax, a
mov type&bx, b
mov a, type&bx
mov b, type&ax
pop type&bx type&ax
endm
SWAP [firstw], [secondw]
SWAP [firstd], [secondd], e
call ExitProcess
end begi
n
А теперь попытаемся составить м/о таким образом, чтобы его можно было использовать и для байтов. В этом случае в качестве регистров можно использовать регистры al
и bl, т.е. параметром является не только первая, но и последняя буква имени регистра:
Программа для этого варианта имеет вид:
IDEAL
p586
model flat
extrn ExitProcess:proc
dataseg
firstd dd 1
secondd dd 2
firstw dw 1
secondw dw 2
firstb db 1
secondb db 2
codeseg
begin:
MACRO SWAP x, y, ft, st
push ft&ax ft&bx
mov ft&a&st, x
mov ft&b&st, y
mov x, ft&b&st
mov y, ft&a&st
pop ft&bx ft&ax
endm
SWAP [firstb], [secondb], ,l
SWAP [firstw], [secondw],,x
SWAP [firstd], [secondd], e,x
call ExitProcess
end begin
Заметим, что в данном случае оставить формальные параметры a, b нельзя, т.к. постоянная часть и параметр совпадают.
Значение фактического параметра вставляется всюду, даже во внуть строки, если используется со знаком &, например
…
MACRO error name
Db ‘Ошибка. Файл &name’
endm
dataseg
msg1 error first.asm
end
Имя макроопределения может совпадать с ключевыми словами, т.е. можно переопределить команды процессора, правда, в этом случае компилятор выдаст диагностическое сообщение о совпадении имен!
Пример замены операции сложения на операцию вычитания:
IDEAL
p586
model flat
extrn ExitProcess:proc
MACRO error name
Msg db'Ошибка. Файл &name'
endm
MACRO add a, b
mov ax, a
sub ax, b
mov a, ax
endm
dataseg
error first.asm
codeseg
begin:
add cx,dx
call ExitProcess
end begin
В этом примере вместо команды add ax,bx используется команда sub ax,bx.
Допускается многократное переопределение макроса в программе. Действует последнее определенное.
Определение внутренних меток
Пусть необходимо в программе вычислять значения максимального элемента из двух заданных. В этом случае макросы предпочтительнее процедур
MACRO MAX x, y, z
push eax
mov eax, x
cmp eax, y
jle m1
mov eax, y
m1:
mov z, eax
pop eax
endm
Пусть в программе необходимо многократно использовать этот макрос. При повторном его использовании будет ошибка в связи с повтором метки. Для обеспечения использования внутренних меток используется директива LOCAL <Метка>, в этом случае компилятор формирует каждый раз уникальную метку. Директива LOCAL должна быть записана сразу после заголовка м/о.
Пример. Составить программу для определения максимального элемента для трех чисел с помощью макросов.
IDEAL
p386
MODEL fLat
extrn ExitProcess:proc
dataseg
x dd 3
y dd 5
z dd 2
u dd ?
codeseg
begin:
MACRO max x, y, z
local m1
push eax
mov eax, x
cmp eax, y
jge m1
mov eax, y
m1:
mov z, eax
endm
max [x], [y], [u]
max [u], [z], [u]
call ExitProcess
end begin
При компиляции программы формируются метки вида ??0000, ??0001 и т.д.
Использование фактических параметров со специальными знаками
Если фактический параметр внутри содержит запятую или пробел, параметр должен быть заключен в скобки <>, например:
IDEAL
p586
model flat
MACRO ERROR msg, number
m&number db '&msg',13,10,'$'
endm
dataseg
error <Ошибка в записи идентификатора>, 1
error <Ошибка в записи числа>, 2
error <Ошибка в записи числа, длина !> 6>, 3
END
Откомпилируйте этот пример с формированием листинга и проанализируйте созданный листинг.
Если среди символов фактического параметра есть символы <>, перед ними ставится знак !, например для предыдущего макроса необходимо сформировать
error <Ошибка в записи идентификатора, длина !>6>, 3
Если знак ! будет пропущен, первый знак > будет рассмотрен как конец параметра.
Если вместо фактического параметра используется выражение, которое должно быть вычислено до подстановки, перед параметром используется знак % например:
MACRO ARRAY name, type, count
name d&type (count) dup(?)
endm
Для использования макроса для выделения памяти под матрицу с данными типа двойное слово размером 25*80 можно записать:
IDEAL
P586
model flat
macro array name, type, count
name d&type (count) dup (?)
endm
dataseg
array matr, d, %25*80
end
Вложенные и рекурсивные макросы
Макроопределение может включать в себя макрокоманды для других макросов или само определение других макросов.
Пример. Пусть необходимо создать макросы для выполнения операций сложения или вычитания, причем операция определяется параметром. Тогда м/о для создания макросов имеет вид:
MACRO CREAT x,y,z,kod
MACRO op&kod x, y, z
push eax
mov eax, x
kod eax, y
mov z, eax
pop eax
endm
endm
Для создания макроса для сложения используется макрокоманда:
creat x, y, z, add
а для вычитания - макрокоманда
creat x, y, z, sub
Рекурсивные макросы будут рассмотрены ниже
Директивы условной трансляции
IF <Выражение>
<Операторы1>
ELSE
<Операторы2>
ENDIF
Операторы1 компилируются, если значение выражения истинно, в противном случае компилируются операторы2. Выражение
считается ложным, если оно равно 0.
В выражениях можно использовать логические операции AND, OR, NOT, знаки отношений: EQ(=), NE (¹), GT(>), GE(³), LT (<), LE (£), а также операции сдвига SHL, SHR.
Есть специальные выражения, позволяющие определить транслируемые команды в зависимости от определенных переменных, передаваемых параметров и т.д.
Список специальных выражений:
ifdef <Переменная>
- Если определена заданная переменная (т.е. для нее есть оператор equ или =);
ifndef <Переменная> - Если не определена заданная переменная;
ifidn <П1>, <П2>
- если совпадают два параметра макрокоманды, или параметр совпадает с некоторой константой;
ifdif <П1>, <П2>
- если не совпадают два параметра макрокоманды, или параметр не совпадает с некоторой константой;
ifb <П1> -
если заданный параметр в макрокоманде пустой (не определен);
ifnb <П1> - если заданный параметр в макрокоманде не пустой (определен);
if1 - если выполняется первый просмотр;
if2 - если выполняется второй просмотр.
Пример. Составить макроопределение для вычисления у = |x| для данных длиной 32 бита.
ideal
p586
model flat
extrn ExitProcess:proc
MACRO _ABS x, y
local m
push eax
mov eax, x
test eax, eax
jns short m
neg eax
m:
mov y, eax
pop eax
endm
codeseg
begin:
mov edx, -5
_ABS edx, ebx
mov edx, 5
_ABS edx, ebx
call ExitProcess
end begin
Имя
_ABS используется, т.к. имя ABS зарезервировано. Макрос работает не верно, если результат в регистре EAX.
Для использования макроса в случае результата в регистре EAX запишем его в виде:
MACRO _ABS x, y
local m
ifidn
r equ
else
r equ
endif
push r
mov r, x
test r, r
jns short m
neg r
m:
mov y, r
pop r
endm
Директива условной трансляции позволяет выбрать рабочий регистр.
Формирование сообщений об ошибках
Пусть для исполнения программы требуется, чтобы переменная TEST была определена. Для проверки этого можно использовать операторы:
IFNDEF TEST
ERR «Определи переменную TEST!»
ENDIF
Если не определена переменная TEST, компилятор сообщает USER ERROR и сообщение «Определи переменную TEST!».
Обзор средств условной трансляции
Позволяют транслировать или не транслировать заданный участок программы, транслировать один и тот же участок повторно или формировать ошибочные сообщения при трансляции программы, при выполнении заданных условий
Примеры необходимости использования средств условной трансляции.
1. Составленные до сих пор программы, сохраняющие содержимое регистров, предполагали, что результат не помещается в рабочие регистры. Если это условие не выполняется, макросы работать не будут. Для обеспечения правильной работы необходимо проверять регистр-приемник.
2. Реализуются циклы с известным числом повторений. Для исключения переходов и сбросов конвейера, связанных с переходом, циклы разворачиваются, т.е. выполняется трансляция участка программы заданное число раз
Безусловное повторение
Общий вид директивы:
REPT count
команды
endm
Команды повторяются заданное число раз.
Пример. Пусть необходимо увеличить на 4 содержимое регистра eах, не изменяя кода переноса (бит с).
Rept 4
inc ax
endm
Директива IRP
IRP Параметр, <П1, П2, ...>
Команды
ENDM
Команды формируются столько раз, сколько задано параметров в угловых скобках. Каждый раз вместо параметра, заданного первым подставляется очередной параметр из списка заданных.
Пример. Составитьмакроопределение для поиска максимального для заданных 5 чисел.
ideal
p586
model flat
extrn ExitProcess:proc
MACRO MAX RES, X1, X2, X3, X4, X5
local m
ifidn
reg equ ebx
else
reg equ eax
endif
push reg
mov reg, X1
IRP X,
local n
cmp reg, X
jge short n
mov reg, X
n:
endm
mov RES, reg
endm
codeseg
begin:
MAX eax, 5, 4, 3, 6, 1
call ExitProcess
end begin
Если значение параметра, который подставляется, состоит из одной буквы, то используется макрос вида IRPC.
Директива IRPC
Общий вид директивы:
IRPC Параметр, Строка
Символы строки подставляются вместо значение параметра.
Пример. Сформировать массив натуральных чисел
ideal
p586
model flat
dataseg
irpc d, 0123456789
c&d db d
endm
end
Для этого примера сформируйте листинг и проверьте полученный сегмент данных.
Директива WHILE
Общий вид директивы WHILE
WHILE (Выражение)
Команды
ENDM
Последний тип макроса используется, если необходимо развернуть цикл с известным числом повторений для исключения команд перехода, а значит возможного сброса конвейера.
Пример. Составить программу сложения чисел заданного массива, не используя команд перехода
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
a dd 1,2,3,4,5
s dd ?
codeseg
begin:
xor eax, eax
xor esi,esi
i=1
while i le 5
add eax, [a + esi]
add esi, 4
i = i + 1
endm
mov [s], eax
call ExitProcess
end begin
Обработка ошибок с помощью функции GetLastError
Общий вид функции:
DWORD GetLastError(VOID)
Функция возвращает код ошибки (32 бита). Список кодов ошибок приведен в файле WinError.h. Здесь же приведен формат кода ошибки:
| Биты | 31-30 | 29 | 28 | 27-16 | 15-0 | ||||||
| № поля | 1 | 2 | 3 | 4 | 5 |
1 – Код степени тяжести ошибки (severity code):
00 – успех;
01 – информация;
10 – предупреждение;
11 – ошибка.
2 – Кем определена ошибка (Microsoft - 0, пользователь -1). Благодаря этому биту можно всегда определять коды ошибок, которые гарантированно не совпадут с системными кодами.
3 – Зарезервировано. Должно быть 0.
4 – Код подсистемы, к которой относится ошибка, например,
#define FACILITY_WINDOWS 8
#define FACILITY_STORAGE 3
#define FACILITY_INTERNET 12
#define FACILITY_CERT 11
Определяется MICROSOFT.
5 – Код ошибки 0..65535.
Ниже приведен фрагмент кода для вывода сообщения, соответствующего ошибке, нв языке, принятом по умолчанию для операционной системы. В этом фрагменте используются функции:
FormatMessage
– для формирования требуемого сообщения по номеру ошибки, который возвращает функция GetLastError. Для этой функции задается адрес буфер, а функция выделяет буфер в куче и записывает сформированное сообщение; тип формируемого сообщения (для нас – сообщение ОС - FORMAT_MESSAGE_FROM_SYSTEM и язык, принятый по умолчанию – макрос MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
MessageBox – для вывода диалога с собщением;
LocalFree – для освобождения памяти, выделенной для буфера.
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL );
MessageBox( NULL, lpMsgBuf, "GetLastError", MB_OK|MB_ICONINFORMATION);
LocalFree( lpMsgBuf );
Составление пользовательских функций со стандартной обработкой ошибок
При составлении пользовательских функций со стандартной обработкой ошибок необходимо:
1. Сформировать код ошибки, при этом в качестве кода можно выбрать стандартный код (см. файл WINERROR.H). Если среди кодов нет подходящего, то сформировать свой и определить его в своем заголовочном файле.
2. В функции перед возвратом обратитьсяк функции SetLastError. Заголовок функции :
VOID SetLastError (DWORD dwCode),
Где dwCode – код ошибки.
Именно этот код будет возвращен функцией GetLastError
Пример. Пусть необходимо возвратитькод: Недостаточно памяти (ERROR_NOT_ENOUGH_MEMORY)
#include
bool MyFun (){
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
return false;
}
int main(int argc, char* argv[])
{
bool b = MyFun ();
if (!b){
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL );
MessageBox( NULL, (char*)lpMsgBuf, "GetLastError",MB_OK|MB_ICONINFORMATION);
LocalFree( lpMsgBuf );
}
return 0;
}
Приведенный код программы выведен сообщение о недостающей памяти, на языке, принятом по умолчанию для операционной системы.
Обзор средств ввода-вывода
При программировании на ассемблере используются:
физический ввод-вывод;
системные вызовы.
Физический ввод-вывод предполагает работу с портами устройств или использование функций BIOS. Системные вызовы - это использование функций операционной системы для организации ввода-вывода. В данном курсе рассматриваются только системные вызовы.
Для системных вызовов используются функции WIN API. В случае ошибок эти функции возвращают значения, которые зависят от типа возвращаемого значения. Код ошибки, по которому можно определить ее причину, определяется с помощью функции WIN API GetLastError, которая должна быть вызвана непосредственно после функции, которая может быть завершена с ошибкой. Для составления безопасных программ рекомендуется после всех функций, которые могут завершаться с ошибкой, делать такую проверку!
Функция MessageBox
| int MessageBoxA( HWND hWnd, // Дескриптор окна (NULL для текущего) LPCTSTR lpText, // Адрес выводимой строки LPCTSTR lpCaption, // Адрес заголовка UINT uType // Стиль. Определяет выводимые кнопки ); Допустимые стили: MB_ABORTRETRYIGNORE (2) 3 кнопки: Abort, Retry, и Ignore. MB_OK (0) : Кнопка OK. MB_OKCANCEL (1) Кнопки: OK и Cancel. MB_RETRYCANCEL (5) Кнопки: Retry и Cancel. MB_YESNO (4) Кнопки : Yes и No. MB_YESNOCANCEL (3) 3 кнопки: Yes, No, иCancel. Значения констант, которые надо задавать вместо приведенных имен при программировании на ассемблере заданы в скобках после определения константы. Их можно посмотреть в файле WINUSER.H Дополнительно, можно задавать иконки для окна и дополнительную информацию (см. файл WIN32.HLP): Возвращаемое значение: Функция возвращает 0, если не достаточно памяти для создания окна. При успешном завершении функция возвращает код нажатой клавиши: IDABORT (3) нажата кнопка ABORT. IDCANCEL (2) нажата кнопка CANCEL или клавиша ESC. IDIGNORE (5) нажата кнопка IGNORE. IDNO (7)нажата кнопка NO. IDOK (1)нажата кнопка OK. IDRETRY(4)нажата кнопка RETRY. IDYES(6)нажата кнопка YES. |
Пример программы для вывода сообщения “Hello, World”
p586
include win.inc
model flat
extrn ExitProcess:proc
extrn MessageBoxA:proc
dataseg
mystr db 'HelloWorld!', 0
tit db 'Message', 0
codeseg
begin:
push 0
push offset tit
push offset mystr
push 0
call MessageBoxA
call ExitProcess
end begin
Функции и константы для работы с консолью
При работе с консолью необходимо:
определить номера стандартных устройств (см. файл winuser.h);
определить дескриптор для заданного устройства;
обратиться к функции ввода – вывода.
Информация о номерах устройств и требуемых функциях приведена ниже.
| Номера стандартный устройств STD_INPUT_HANDLE (DWORD)-10 ввод STD_OUTPUT_HANDLE (DWORD)-11вывод STD_ERROR_HANDLE (DWORD)-12 вывод ошибок Определение дескриптора устройства: HANDLE GetStdHandle( DWORD nStdHandle // номер устройства ); Если ошибка, функция возвращает INVALID_HANDLE_VALUE, которому соответствует значение –1. Ввод данных со стандартного устройства: BOOL ReadConsole( HANDLE hConsoleInput, // дескриптор консоли LPVOID lpBuffer, // адрес буфера, куда читать данные DWORD nNumberOfCharsToRead, // количество читаемых символов LPDWORD lpNumberOfCharsRead, // адрес количества прочитанных символов LPVOID lpReserved // резерв, должен быть NULL ); Функция возвращает true в случае успеха и false в случае ошибки[19] BOOL WriteConsole( HANDLE hConsoleOutput, // дескриптор консоли CONST VOID *lpBuffer, // адрес буфера для записи DWORD nNumberOfCharsToWrite, // количество символов LPDWORD lpNumberOfCharsWritten, // адрес количества символов, которые действительно записаны LPVOID lpReserved // Резерв, должен быть NULL ); |
Пример программы для ввода – вывода строки символов.
ideal
p386
model flat
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn ReadConsoleA:proc
extrn WriteConsoleA:proc
dataseg
include "win.inc"
din dd 0
dout dd 0
buf db 80 dup (?)
siz dd ?
mystr db 'Input string, please', 13, 10, 0
codeseg
begin:
push STD_INPUT_HANDLE
push str1
push 0
call MessageBoxA
endm
Файл для проверки макроса
Ideal
p586
model flat
extrn ExitProcess:proc
extrn MessageBoxA:proc
include "win.inc"
dataseg
t db 'Information', 0
text db 'Hello, world!', 0
codeseg
begin:
ShowMessage
call ExitProcess
end begin
Пример 2. Составить функцию gets для ввода строки с клавиатуры, которая в результате формирует строку с нулевым завершителем. Признак конца вводимой строки- символ
ideal
p586
model flat
include "win.inc"
extrn GetStdHandle:proc
extrn ReadConsoleA: proc
codeseg
proc gets
public gets
arg mystr:dword
local s:dword=r
push ebp
mov ebp, esp
sub esp, r
push ebx ecx
push STD_INPUT_HANDLE
call GetStdHandle; ebp-0; eip-4; ;&str-8
mov ebx, [ebp+8]
push 0
lea ecx, [ebp-4]
push ecx
push 80 ebx eax
call ReadConsoleA
sub [s], 2
add ebx, [s]
mov [byte ptr ebx], 0
mov eax, [ebp+8]
pop ecx ebx
mov esp, ebp
pop ebp
ret 4
endp
end
Главная программа для проверки функции ввода строки имеет вид:
ideal
p386
model flat
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn ReadConsoleA:proc
extrn WriteConsoleA:proc
extrn gets:proc
dataseg
include "win.inc"
din dd 0
dout dd 0
buf db 80 dup (?)
siz dd ?
mystr db 'Input string, please', 13, 10, 0
codeseg
begin:
push STD_INPUT_HANDLE
call GetStdHandle
mov [din], eax
push STD_OUTPUT_HANDLE
call GetStdHandle
mov [dout], eax
push 0 offset siz 22 offset mystr [dout]
call WriteConsoleA
push offset buf
call gets
call ExitProcess
end begin
Пример 3. Составить функцию для вывода строки с нулевым завершителем на стандартное устройство
Функция:
Ideal
p586
model flat
include "win.inc"
extrn GetStdHandle:proc
extrn ReadConsoleA: proc
extrn WriteConsoleA:proc
dataseg
buffer db 80 dup (?)
codeseg
proc gets
public gets
arg mystr:dword
local s:dword=r
push ebp
mov ebp, esp
sub esp, r
push ebx ecx
push STD_INPUT_HANDLE
call GetStdHandle;
mov ebx, [ebp+8]
push 0
lea ecx, [ebp-4]
push ecx
push 80 ebx eax
call ReadConsoleA
sub [s], 2
add ebx, [s]
mov [byte ptr ebx], 0
mov eax, [ebp+8]
pop ecx ebx
mov esp, ebp
pop ebp
ret 4
endp
proc puts
public puts
arg mystr1:dword
local s1:dword=r1
push ebp
mov ebp, esp
sub esp, r1
push ebx ecx edx esi
push STD_OUTPUT_HANDLE
call GetStdHandle
mov ebx, [ebp+8]; &
; len
mov edx, 0
mov esi, offset buffer
m2:
mov cl, [ebx+edx]
mov [esi+edx], cl
test cl, cl
jz short m1
inc edx
jmp m2
m1:
push 0
lea ecx, [ebp-4]
push ecx
push edx
push offset buffer
push eax
call WriteConsoleA
mov eax, [ebp+8]
pop esi edx ecx ebx
mov esp, ebp
pop ebp
ret 4
endp
end
Главная программа для проверки функции вывода строки имеет вид:
Ideal
p386
model flat
extrn ExitProcess:proc
extrn GetStdHandle:proc
extrn ReadConsoleA:proc
extrn WriteConsoleA:proc
extrn gets:proc
extrn puts:proc
dataseg
include "win.inc"
din dd 0
dout dd 0
buf db 80 dup (?)
siz dd ?
mystr db 'Input string, please', 13, 10, 0
codeseg
begin:
push offset mystr
call puts
push offset buf
call gets
push offset buf
call puts
call ExitProcess
end begin
Функции ввода-вывода для стандартных устройств
Известно, что кодировка символов , принятая по умолчанию, отличается для WINDOWS 95, WINDOWS NT. Тип кодировки задается буквой A в конце функции для однобайтной кодировки и буквой W для 2-х байтной. При создании программ на языке АССЕМБЛЕР необходимо учитывать это различие.
Для программного определения типа установленной системы используется функция GetVersion:
| DWORD GetVersion(VOID) Функция возвращает номер версии и подверсии установленной системы Для всех платформ младшее слово содержит номера версии (младший байт номер версии, старший – номер подверсии в 16 –ой системе счисления) Для различения платформ ОС используется старший бит и младший байт, которые принимают значения, приведенные в табл. 13.1. Таблица 13.1. Типы операционных систем |
| Платформа | Старший бит | Младший байт | |||
| Windows NT | 0 | 3 или 4 | |||
| Windows 95 | 1 | 4 | |||
| Win32s, Windows 3.1 | 1 | 3 |
Для Windows NT и Win32s, оставшиеся биты старшего слова задают номер разработчика, для Windows 95 – резервируются.
Пример использования функции на С++:
…
DwVersion = GetVersion();
dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
dwWindowsMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
if (dwVersion < 0x80000000) // Windows NT
dwBuild = (DWORD)(HIWORD(dwVersion));
else if (dwWindowsMajorVersion < 4) // Win32s
dwBuild = (DWORD)(HIWORD(dwVersion) & ~0x8000);
else // Windows 95 --
dwBuild = 0;
К функциям ввода – вывода для стандартных устройств относятся:
функция MessageBox для вывода диалогового окна с заданной информацией и заданными кнопками;
функции для работы с консолью
По умолчанию в качестве стандартных устройств ввода используется клавиатура, а вывода -монитор. Выводимую информацию можно разделить на информацию, соответствующую штатной работе программы, и информацию, соответствующую реакции на ошибки. По умолчанию для обоих типов информации используется монитор. Допускается переназначение стандартных устройств. Ниже приведены наиболее распространенные константы и функции[18].
Ввод/вывод числовых данных
Для ввода числовых данных необходимо:
1. Ввести соответствующую строку.
2. Преобразовать ее в числовой вид.
Для вывода необходимо выполнить действия в обратном порядке.
Преобразование строки в число.
Пусть задана строка вида:
[<Пробелы>] [<>][<Пробелы>] Цифра [Цифра Цифра...]
Преобразовать ее в соответствующее ей число.
Для преобразования последовательности цифр в число можно использовать схему Горнера. Например, пусть необходимо строку символов «375»
преобразовать в число. Представим число в виде:
(3 * 10 + 7) * 10 + 5. Здесь 3, 7, 5 -цифры числа, а 10
- основание системы счисления для исходного числа. Таким образом, алгоритм преобразования:
r=0;
for (i=n-1; i>=0; i--)
r = r*10 +d[i];
Так как в общем виде числа могут быть пробелы, которые надо игнорировать, напишем макрос для игнорирования этих символов. На вход макроса подается адрес символа строки, на выходе получаем адрес ненулевого символа
macro skip address
local l1, l2
ifidn
reg equ esi
else
reg equ edi
endif
push reg
mov reg, address
l2:
cmp [byte ptr reg], ‘ ’
jne l1
inc reg
jmp l2
l1:
mov address, reg
pop reg
endm
Данный макрос подключим к файлу win.inc
Функции для преобразования строки в число и обратного преобразования представлены ниже.
Преобразование чисел при выводе
До вывода числа оно должно быть преобразовано из внутреннего представления в строковое. Для преобразования можно использовать следующие способы.
1. Число делится на 10 и остаток от деления рассматривается как очередная цифра. Деление продолжается до тех пор, пока не получим нулевое значение. Недостаток способа: цифры числа получаем, начиная с младших, перед выводом необходимо инвертирование строки или формирование строки заданной длины.
2. Число делится на 10000, 1000,... и получаем цифры числа, начиная со старших цифр. Недостаток. Требуется хранение массива или его программное формирование.
Рассмотрим реализацию первого способа. Для хранения 32-битного десятичного числа требуется 10 цифр + знак + нулевой завершитель, т.е. 12 символов. Так как строка может занимать не все 12 символов, оставшиеся символы слева должны быть пробелами.
Функция для преобразования числа в строку. Входное данное-32-битное число. Выходное - строка с нулевым завершителем
Функции для преобразования числа при вводе и выводе и соответствующая главная программа представлены ниже:
Ideal
p586
model flat
extrn ExitProcess:proc
include "win.inc"
dataseg
my db ' 25', 0
val dd ?
codeseg
proc atoi
public atoi
arg value:dword, mystr:dword = p
push ebp
mov ebp, esp
push ebx ecx edx esi
mov esi, [mystr]
skip esi
mov ebx, 1
cmp [byte ptr esi], '-'
je short minus
cmp [byte ptr esi], '+'
je short plus
jmp prod
minus:
inc esi
neg ebx
jmp prod
plus:
inc esi
prod:
skip esi
xor eax, eax; r=0
mov ecx, 10
for:
cmp [byte ptr esi], 0
je exit
mul ecx
test edx, edx
jne error
mov dl, [esi]
sub dl, '0'
add eax, edx
inc esi
jmp for
exit:
mul ebx
mov esi, [value]
mov [esi], eax
xor eax, eax
jmp lend
error:
mov eax, 1
lend:
pop esi edx ecx ebx
pop ebp
ret p
endp atoi
begin:
push offset my offset val
call atoi
call ExitProcess
end begin
Задание для самостоятельной работы. Составить программу для второго алгоритма и сравнить их по вычислительной сложности.
Обзор системных вызовов для организации ввода-вывода
В зависимости от типа устройств, используемых для ввода -вывода, функции делятся на функции ввода-вывода для стандартных устройств (например, клавиатура, дисплей) и функции ввода-вывода для нестандартных устройств (например, диски).
Обзор функций WINDOWS API для работы с файлами
Файл – это объект операционной системы. Это значит, что ОС создает для каждого файла таблицу, куда заносит информацию о файле, включающую в себя имя файла, его местоположение на диске, текущее положение указателя, режимы открытия и т.д.
Для создания записи о файле используется функция CreateFile:
HANDLE CreateFile(
LPCTSTR lpFileName, // Имя файла (с нулевым завершителем)
DWORD dwDesiredAccess, // Режим доступа к файлу (read-write)
DWORD dwShareMode, // Возможность share
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // атрибуты безопасности
DWORD dwCreationDistribution, // Режимы создания
DWORD dwFlagsAndAttributes, // Атрибуты файла
HANDLE hTemplateFile // Дескриптор файла с атрибутами копирования
);
Параметр dwDesiredAccess может принимать значения.
GENERIC_READ Разрешено чтение.
GENERIC_WRITE Разрешена запись. Может задаваться совместно с первым.
(см. файл WINNT.H)
#define GENERIC_READ (0x80000000L)
#define GENERIC_WRITE (0x40000000L)
#define GENERIC_EXECUTE (0x20000000L)
#define GENERIC_ALL (0x10000000L)
DwShareMode
-возможность совместного использования. Если параметр равен 0, файл нельзя совместно использовать.
Допустимые значения параметров:
FILE_SHARE_DELETE Windows NT only: Разрешается только тогда, когда файл удаляется.
FILE_SHARE_READ Разрешается только для чтения.
FILE_SHARE_WRITE Разрешается только для записи.
LpSecurityAttributes – указатель на структуру SECURITY_ATTRIBUTES с целью задания возможности наследования файла процессом – потомком. Если параметр равен NULL, дескриптор файла не может наследоваться.
Windows NT: lpSecurityDescriptor определяет дескриптор безопасности для дескриптора. Если он равен NULL, дескриптор получает параметры безопасности, принятые по умолчанию, если файловая система поддерживает такие атрибуты. Для Windows 95 эта структура игнорируется.
Функция ReadFile читает данные из файла.
BOOL ReadFile(
HANDLE hFile, // Дескриптор файла
LPVOID lpBuffer, // адрес буфера
DWORD nNumberOfBytesToRead, // количество читаемых байтов
LPDWORD lpNumberOfBytesRead, // адрес количества прочитанных байтов
LPOVERLAPPED lpOverlapped // адрес тсруктуры для данных
);
Если возвращается True, а количество прочитанных байтов равно 0, то это прочитан конец файла.
BOOL WriteFile(
HANDLE hFile, // Дескриптор файла
LPCVOID lpBuffer, // адрес буфера
DWORD nNumberOfBytesToWrite, // количество записанных байтов
LPDWORD lpNumberOfBytesWritten, // указатель на количество записанных байтов
LPOVERLAPPED lpOverlapped // Указатель на структуру для асинхронного использования файла
);
Пример 1. Создать файл для записи и записать туда заданную строку.
Ideal
p586
model flat
extrn ExitProcess:proc
extrn CreateFileA:proc
extrn CloseHandle:proc
extrn ReadFile:proc
extrn WriteFile:proc
extrn CloseHandle:proc
extrn MessageBoxA:proc
include "win.inc"
dataseg
my db ' - 25', 0
val dd ?
d1 dd ?
d2 dd ?
n1 db 'a.txt', 0
n2 db 'b.txt', 0
msg db 'Error', 0
s1 dd ?
codeseg
begin:
push 0 0 CREATE_ALWAYS 0 0
push GENERIC_WRITE
push offset n1
call CreateFileA
cmp eax, INVALID_HANDLE_VALUE
jne short m1
push MB_OK offset msg offset n1 0
call MessageBoxA
jmp short m2
m1:
mov [d1], eax
mov eax, 0
for1:
mov bl, [my+eax]
test bl, bl
je short break
inc eax
jmp for1
break:
push 0 offset s1 eax offset my [d1]
call WriteFile
test eax, eax
jne short m4
push MB_OK offset msg offset n1 0
call MessageBoxA
jmp short m2
m4:
push [d1]
call CloseHandle
cmp eax, INVALID_HANDLE_VALUE
jne short m3
push MB_OK offset msg offset n1 0
call MessageBoxA
jmp short m2
m3:
m2:
call ExitProcess
end begin
Пример 2. Составить функцию для копирования файлов с заданными именами.
Ideal
p586
model flat
extrn ExitProcess:proc
extrn CreateFileA:proc
extrn CloseHandle:proc
extrn ReadFile:proc
extrn WriteFile:proc
extrn CloseHandle:proc
extrn MessageBoxA:proc
include "win.inc"
dataseg
my db ' - 25', 0
val dd ?
n1 db 'io6.exe', 0
n2 db 'proba.txt', 0
msg db 'Error', 0
s1 dd ?
codeseg
proc FileCopy
public FileCopy
arg nin:dword, nout:dword
local buffer:byte:512, d1:dword, d2:dword, count:dword=s
push ebp
mov ebp, esp
sub esp, s
push eax
push 0 0 OPEN_EXISTING 0 0 GENERIC_READ [nin]
call CreateFileA
cmp eax, INVALID_HANDLE_VALUE
jne short @@m1
push MB_OK offset msg [nin] 0
call MessageBoxA
jmp @@m3
@@m1:
mov [d1], eax
push 0 0 CREATE_ALWAYS 0 0 GENERIC_WRITE [nout]
call CreateFileA
cmp eax, INVALID_HANDLE_VALUE
jne short @@m2
push MB_OK offset msg [nout] 0
call MessageBoxA
jmp @@m3
@@m2:
mov [d2], eax
for1:
push 0
lea eax, [count]
push eax 512
lea eax, [buffer]
push eax [d1]
call ReadFile
test eax, eax
jne short @@m4
push MB_OK offset msg [nin] 0
call MessageBoxA
jmp short @@m3
@@m4:
mov eax, [count]
test eax, eax
je short break
push 0
lea eax, [count]
push eax [count]
lea eax, [buffer]
push eax [d2]
call WriteFile
test eax, eax
jne short @@m5
push MB_OK offset msg [nout] 0
call MessageBoxA
jmp short @@m3
@@m5:
mov eax, [count]
cmp eax, 512
jb short break
jmp for1
break:
push [d1]
call CloseHandle
push [d2]
call CloseHandle
@@m3:
pop eax
mov esp, ebp
pop ebp
ret 8
endp
begin:
push offset n2 offset n1
call FileCopy
call ExitProcess
end begin
Удаление файлов
BOOL DeleteFile(
LPCTSTR lpFileName // pointer to name of file to delete
);
lpFileName – имя файла – строка с нулевым завершителем
Windows 95: удаляет даже открытый файл. Для предотвращения потери данных файл надо сначала закрыть.
Windows NT: Не удаляет открытых файлов.
Копирование или перемещение файлов
До копирования файл должен быть закрыт или открыт только для чтения. Для этого используются функции CopyFile или CopyFileEx.
Для перемещения файла он должен быть закрыт. Для этого используются функции MoveFile и MoveFileEx, которые копируют файл в новый и удаляют старый.
Копирование существующего файла в новый.
BOOL CopyFile(
LPCTSTR lpExistingFileName, // Имя существующего файла
LPCTSTR lpNewFileName, // Имя результата
BOOL bFailIfExists // Флаги для определения действий для //существующего файла
);
bFailIfExists= TRUE
и новый файл уже существует, то ошибка;
bFailIfExists= FALSE и новый файл уже существует, то файл пересоздается.
Замечание:
Атрибуты безопасности не копируются в результирующий файл.
Файловые атрибуты (FILE_ATTRIBUTE_*) копируются, например, если исходный файл был FILE_ATTRIBUTE_READONLY, то результирующий файл будет тоже только для чтения
Функция MoveFile переименовывает существующий файл или каталог, переносит, если надо с устройства на устройство и, если это каталог, то переносит все вложенные подкаталоги.
BOOL MoveFile(
LPCTSTR lpExistingFileName, // Имя существующего файла или каталога
LPCTSTR lpNewFileName // Новое имя файла или каталога
);
lpExistingFileName – имя существующего файла или каталога;
lpNewFileName – имя нового файла или каталога. Новое имя не должно существовать. Новое имя файла может соответствовать устройству с другой файловой системой. Новый каталог должен быть на том же устройстве.
Рекомендуем выполнить копирование (перемещение) файлов, используя стандартные функции WIN API и функции языка СPP и сравнить их по производительности!
Блокирование участков файлов
Так как ОС разрешает одновременную работу с файлом нескольких приложений, может возникнуть необходимость блокирования участка файла на некоторый промежуток времени.
Функции LockFile и LockFileEx блокируют заданный участок файла. При обращении к заблокированной области для чтения или записи – всегда ошибка..
Функция LockFileEx даже позволяет указать, особые свойства блокирования. (exclusive)
блокирование запрещает полный доступ всем процессам, в том числе и чтение, и запись. (shared) блокирование запрещает только запись. Это позволяет определить в файле область только для чтения.
Для разблоктрования области используются функции UnlockFile
или UnlockFileEx. Приложение должно разблокировать все области файла перед его закрытием
Функция LockFileEx блокирует участок окрытого файла, задавая shared или exclusive доступ.
BOOL LockFileEx(
HANDLE hFile, // Дескриптор файла
DWORD dwFlags, // Флаги
DWORD dwReserved, // Резерв, 0
DWORD nNumberOfBytesToLockLow, // младшие 32 бита длины //области блокирования
DWORD nNumberOfBytesToLockHigh, // старшие 32 бита длины
// области блокирования
LPOVERLAPPED lpOverlapped // Адрес стрктуры с оределением //области блокирования
);
Значения флагов:
LOCKFILE_FAIL_IMMEDIATELY Если это значение установлено и немедленно нельзя заблокировать заданный участок – сразу ошибка, иначе ждет возможности.
LOCKFILE_EXCLUSIVE_LOCK Блокирование типа exclusive. Иначе типа shared.
Структура содержит значение смещения для блокируемого участка (поля Offset, OffsetHigh).
typedef struct _OVERLAPPED { // o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
Функция UnlockFileEx разблокирует заблокированную область открытого файла.
BOOL UnlockFileEx(
HANDLE hFile, // Дескриптор
DWORD dwReserved, // Резерв, 0
DWORD nNumberOfBytesToUnlockLow, // Младшие 32 бита
DWORD nNumberOfBytesToUnlockHigh, // Старшие 32-бита
LPOVERLAPPED lpOverlapped // Адрес структуры со смещением
);
Функция FindFirstFile
HANDLE FindFirstFile(
LPCTSTR lpFileName, // Имя образца для поиска
LPWIN32_FIND_DATA lpFindFileData // Указатель на структуру с результатом
);
Windows 95: Образец для поиска может содержать символы (* и ?). Строка должна быть с нулевым завершителем и по длине не превосходить MAX_PATH
символов.
Windows NT: Ограничение на длину строки снимается, т.к. есть возможность использовать широкую версию (W) функции FindFirstFile. Символы "\\?\" говорят о возможности использования пути длиннее MAX_PATH. Она также работает с именами, заданными в UNICODE (UNC). Символы "\\?\" игнорируются как часть каталога. Например, путь "\\?\C:\myworld\private" интерпретируется как "C:\myworld\private", а "\\?\UNC\bill_g_1\hotstuff\coolapps"
интерпретируется как
"\\bill_g_1\hotstuff\coolapps".
LpFindFileData - Структура WIN32_FIND_DATA.
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; // Атрибуты файла
FILETIME ftCreationTime; // Время создания
FILETIME ftLastAccessTime; // Время последнего доступа
FILETIME ftLastWriteTime; // Время последней записи
DWORD nFileSizeHigh; //Размер файла – старшая часть
DWORD nFileSizeLow; //Размер файла - младшая часть
DWORD dwReserved0; // Резерв
DWORD dwReserved1; // Резерв
TCHAR cFileName[ MAX/_PATH ]; // Имя файла
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;
dwFileAttributes - Значение атрибутов, определяется битами:
FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED,
FILE_ATTRIBUTE_DIRECTORY , FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_NORMAL
FILE_ATTRIBUTE_OFFLINE- данные из фала сразу же недоступны, они могут быть пока в памяти.
FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_SYSTEM файл является частью ОС или используется только ею.
FILE_ATTRIBUTE_TEMPORARY- временный файл-используется для временного хранения. Приложение должно писать в него, только если это абсолютно необходимо . Большинство данных остается в памяти без сброса их на диск, т.к. файл очень скоро удаляется.
Функция FindNextFile
Используется для продолжения поиска по образцу, определенному функцией FindFirstFile.
BOOL FindNextFile(
HANDLE hFindFile, // Дескриптор для поиска
LPWIN32_FIND_DATA lpFindFileData // Указатель на структуру с //данными о файле
);
Для определения причины ошибки – используется функция GetLastError, которая возвращает ERROR_NO_MORE_FILES, если файлов больше нет.
Функция FindClose
Завершает поиск, начатый функцией FindFirstFile и продолженный FindNextFile.
BOOL FindClose(
HANDLE hFindFile // Дескриптор поиска
);
Пример. Вывести текущий каталог.
Ideal
p586
model flat
extrn ExitProcess:proc
extrn FindFirstFileA:proc
extrn FindNextFileA :proc
extrn FindClose :proc
extrn MessageBoxA:proc
extrn puts:proc
include win.inc
dataseg
FileName db '*.*', 0
FindFileData WIN32_FIND_DATA<>
d1 dd ?
msg db 'Error!!!', 0
tit db '',0
eol db 13,10,0
codeseg
begin:
lea eax, [ FindFileData]
push eax
push offset FileName
call FindFirstFileA
cmp eax, INVALID_HANDLE_VALUE
je short error
mov [d1], eax
lea ebx, [FindFileData.cFileName]
push ebx
call puts
push offset eol
call puts
for1:
lea ebx, [FindFileData]
push ebx
push [d1]
call FindNextFileA
test eax, eax
je short break;
lea ebx, [FindFileData.cFileName]
push ebx
call puts
push offset eol
call puts
jmp for1
break:
push [d1]
call FindClose
jmp short ok
error:
ok:
call ExitProcess
end begin
Файл вставки (win.inc)
MB_OK equ 0
STD_INPUT_HANDLE equ -10
STD_OUTPUT_HANDLE equ 11
STD_ERROR_HANDLE equ -12
CREATE_NEW equ 1
CREATE_ALWAYS equ 2
OPEN_EXISTING equ 3
OPEN_ALWAYS equ 4
TRUNCATE_EXISTING equ 5
GENERIC_READ equ 80000000h
GENERIC_WRITE equ 40000000h
GENERIC_EXECUTE equ 20000000h
GENERIC_ALL equ 10000000h
INVALID_HANDLE_VALUE equ (-1)
ideal
macro ShowMessage str1, tit, button
push button
push tit
push str1
push 0
call MessageBoxA
endm
macro skip address
localааа l1, l2
ifidn
regаааааа equааааа esi
else
regаааааа equааааа edi
endif
pushаааа reg
movаааа reg, address
l2:
cmpаааа [byte ptr reg], ' '
jneаааааа l1
incаааааа reg
jmpааааа l2
l1:
movаааа address, reg
popааааа reg
endm
MAX_PATHааа equааааа 260
masm
WIN32_FIND_DATA struc
dwFileAttributes ddаааа ?
ftCreationTimeааааааааааа ddааааааа ?,?
ftLastAccessTime ddааа ?,?
ftLastWriteTimeа ddаааа ?,?
nFileSizeHighа ddааааааа ?
nFileSizeLowаа ddааааааа ?
dwResааааааааааааа ddааааааа 0,0
cFileName dbа (MAX_PATH) dup (?)
cAlternateFileName db 14 dup (?)
ends
+сЁрЄшЄх тэшьрэшх, ўЄю фы ЁрсюЄv ёю ёЄЁєъЄєЁющ яхЁхъы¦ўхэ Ёхцшь ЄЁрэёы Ўшш.
Функции для поиска файлов
Поиск можно выполнять по заданной маске с помощью функций FindFirstFile, FindFirstFileEx, FindNextFile, и FindClose. Образец может включать в себя символы ?, *.
Функции FindFirstFile и FindFirstFileEx создают дескрипторы, котрые используются функцией FindNextFile. Все функции возвращают информацию о найденном файле, которая включает в себя имя файла, его размер, атрибуты и время.
Функция FindClose разрушает дескрипторы, созданные функциями FindFirstFile и FindFirstFileEx.
Приложение может также искать каталог(Функция SearchPath)
Создание временных файлов
Для создания временных файлов используется функция GetTempPath, определяющая каталог, в котором создается временный файл
DWORD GetTempPath(
DWORD nBufferLength, // размер (в символах) буфера для имени //каталога
LPTSTR lpBuffer // адрес буфера для каталога
);
Возвращаемое значение
При успешном завершении – количество символов в буфере, куда записан каталог с нулевым завершителем. Если возвращаемое значение больше, чем задано, фактически возвращается требуемое значение.
Ошибка – 0.
Замечание.
Каталог для временных файлов формируется так:
1. Каталог определяется переменной среды TMP.
2. Каталог определяется переменной среды TEMP, если TMP не задана.
3. Текущий каталог, если TMP и TEMP не заданы.
Для формирования имени временного файла используется функция GetTempFileName.
Имя файла является объединением каталога для временного файла и строки – префикса, формируемой из заданного целого в 16-ой системе счисления. Расширение файла всегда TMP.
Заметим, что функция создает имя файла, но не сам файл. Если задан 0 в качестве целого, функция создает уникальное имя файла.
UINT GetTempFileName(
LPCTSTR lpPathName, // Имя каталога для временного файла
LPCTSTR lpPrefixString, // Строка с префиксом
UINT uUnique, // Число, которое используется для создания имени //файла
LPTSTR lpTempFileName // Адрес буфера для имени файла
);
lpPathName – указатель на строку с нулевым завершителем. Строка должна содержать символы в кодировке ANSI. Обычно задается (.), т.е. текущий каталог или результат работы функции GetTempPath. Если этот параметр равен NULL, функция завершается с ошибкой.
lpPrefixString- Указатель на строку с нулевым завершителем. Функция использует первые 3 символа этой строки как префикс имени файла. Символы необходимо задавать в кодировке ANSI.
uUnique – беззнаковое целое, которое функция преобразует в 16-ую строку и использует при создании имени файла. Если uUnique != 0, функция добавляет эту строку к префиксу для формирования имени. В этом случае функция не создает заданный файл и не проверяет его имя на уникальность. Если uUnique = 0, функция формирует 16-ую строку по текущему системному времени. В этом случае формируются новые значения (++) до тех пор, пока не получим уникальное имя файла, и затем она создает файл в заданном каталоге, а потом его закрывает.
lpTempFileName – сюда заносится сформированное имя файла в кодировке ANSI.
Буфер должен быть достаточной длины (MAX_PATH).
Возвращаемое значение
Успех – функция возвращает уникальное число, которое использовалось при формировании имени файла. Если uUnique не равен 0, то возвращается это значение.
При ошибке функция возвращает 0 ( GetLastError)
Таким образом формат имени:
path\preuuuu.TMP
где
path путь;
pre первые 3 символа префикса
uuuu 16-ое значение uUnique
Когда Windows перезагружается, временные файлы, созданные этой функцией, автоматически не удаляются. Для преобразования имени файла из формата ANSI в Windows строку, необходимо использовать функцию CreateFile для создания временного файла.
Дополнительные функции для работы с файлами
Как показано ранее, использование функций WINAPI в программе на ассемблере и С++, существенно не различается, поэтому ниже в качестве примеров используются примеры на С++.
Позиционирование файла
Для установки требуемой позиции в файле используется функция SetFilePointerEx:
BOOL SetFilePointerEx( HANDLE hFile, // handle to file LARGE_INTEGER liDistanceToMove, // bytes to move pointer PLARGE_INTEGER lpNewFilePointer, // new file pointer DWORD dwMoveMethod // starting point);
hFile – дескриптор открытого файла, который создан в режиме GENERIC_READ или GENERIC_WRITE.
liDistanceToMove – задает смещение в файле, на которое необходимо сместиться;
lpNewFilePointer- переменная, куда записывается новое значение указателя;
dwMoveMethod –определяет, относительно чего смещать. Может принимать значения:
FILE_BEGIN – смещать относительно начала файла;
FILE_CURRENT – смещать относительно ьекущего указателя в файле;
FILE_END – смещать относительно конца файла.
Примеры.
Пример 1. Составить функцию для определения размера файла.
Особенности процессоров с MMX технологией с точки зрения программиста
Для процессоров MMX обеспечивается полная совместимость с предыдущими процессорами и операционными системами. Это означает, что программы, написанные для обычного PENTIUM будут нормально работать для PENTIUM MMX.
Добавлен новый тип данных размером 64 бита.
Для данных можно использовать регистры FPU, которые в этом случае обозначаются MM0..MM7, при этом MM0 соответствует ST0, MM1®ST1, ¼ независимо от вершины стека.
Обеспечено параллельное выполнение операций для частей 64 битного данного размером 1 байт (8 частей), 2 байта (4 части), 4 байта (2 части). Такая технология называется SIMD (Single Instruction Multiple Data)- одна команда для множества данных. Это делает более эффективными операции для:
* видео;
* графики;
* обработки зображений;
* обработки аудио информации;
* скоростной синтез и сжатие;
* телефония и т.д.
Используется 8 регистров MMX длиной 64 бита. Эти регистры для pentium, pentium II совмещены с регистрами FPU, поэтому нельзя одновременно использовать команды FPU и команды MMX. Для pentium Ø èñïîëüçóþòñÿ äëÿ îáåèõ öåëåé ðàçíûå ðåãèñòðû.
Используется 2 типа арифметик:
арифметика с насыщением (saturating) - если результат превосходит предельное значение, он считается равным предельному (цвет не может быть белее белого и чернее черного);
обычная арифметика с отбрасыванием переносов.
Арифметические команды
Реализованы операции сложения, вычитания и умножения. Операции сложения и вычитания выполняются для 1, 2, 4, 8 байтов. Для команды умножения используется режим умножения 4-х пар знаковых чисел по 16 бит каждое. В результате умножения формируются младшие (команда PMULLW) и старшие 64 бита результата (PMULHW). Для использования этой команды для чисел многократной точности требуется предварительное преобразование числа таким образом, чтобы биты 15 и 31 были всегда 0. Вопросы эффективности умножения длинных чисел с помощью команд MMX требует дополнительного исследования.
Наряду с элементарными арифметическими операциями используется операция умножения со сложением (PMADDWD). Сначала перемножаются соответствующие 16-битные элементы, в результате получаем четыре 32 битных элемента P0, P1, P2, P3 (знаковое умножение), а затем вычисляется два 32-битных элемента R0 = P0 + P1; R1 = P2+P3. Все операции выполняются с учетом знака.
Команды сравнения
Общий вид команды:
PCMP<Условие>, например
PCMPGT - проверка на больше;
PCMPEQ - проверка на равно;
Условие задается как в выражениях.
В результате выполнения команды в первый операнд записывается 1, если условие истинно и 0 в противном случае. Команда, как и остальные команды, может одновременно выполняться для нескольких элементов.
Команды преобразования
Предназначены для изменения длины числа в сторону уменьшения (усечение старших разрядов или насыщения), увеличения (расширения знаковым разрядом), например
PACKSS - упаковка со знаковым насыщением;
PUNPCKH - расширение данного, буква H в конце означает, что формируется старшая часть результатов;
PUNPCKL - тоже для младшей части.
Логические крманды
Все логические команды используются для 64 битного числа (почему?).
Кроме традиционных команд (PAND, POR, PXOR, PNOT ) есть команда PANDN (AND + NOT).
Команды сдвига
Логический сдвиг выполняется для 2, 4 и 8 байтовых данных.
Команды:
PSSL - сдвиг влево;
PSRL - сдвиг вправо.
Арифметический сдвиг выполняется для 2, 4 байтовых данных.
Команда:
PSRA - сдвиг вправо;
Команды пересылки
Используются для пересылки 32-битных и 64 битных данных. Выполняют пересылку между памятью и MMX регистром, а также между MMX регистрами. 32-битный вариант возможно использовать для пересылки между регистром общего назначения и MMX регистром.
MOVD - пересылка 32-битных данных;
MOVQ - пересылка 64 битных данных.
Управление режимами работы процессора
Напоминаем, что одновременное использование команд MMX и команд FPU не допускается для процессоров, в которых команды MMX и FPU физически используют одни и те же регистры[20]. Вначале автоматически инициализируется режим использования FPU. Первая команда, относящаяся к классу команд MMX переключает процессор в режим использования MMX. Для переключения режима MMХ в режим FPU используется команда EMMS. Команда без операндов. В этом случае все регистры с плавающей точкой считаются свободными. Рекомендуется эту команду писать всегда перед выходом из функции, которая использовала команды MMX[21].
Обзор команд для MMX
Команды делятся на:
* команды пересылки;
* арифметические команды;
* команды сравнения;
* команды преобразования;
* логические команды;
* команда для переключения режима MMX - FPU
* команды сдвига.
Общий вид кода команды:
P<код>{U|S}[S]{B|W|D|Q},
где:
P - признак команды для MMX (PACK - упакованный);
<код> - код операции, например, MOV, ADD,...
U|S - Беззнаковое/знаковое
S - с насыщением
B|W|D|Q - типы данных
Примеры команд: PADDSB, PSUBUSW.
Требования к операндам:
Первый операнд (SRC) - MMX регистр или память.
Исключение: Команда PMOVD может содержать регистр общего назначения.
Второй операнд (DEST) - MMX регистр.
Использование команд MMX в приложениях
Для создания мобильных приложений, которые могут исполняться для процессоров, поддерживающих технологию MMX и не поддерживающих ее рекомендуется создавать 2 варианта кода и выбирать оптимальный программным путем.
Для определения возможности использования команд MMX для данного процессора используется команда CPUID, EAX = 1. Если технология MMX поддерживается, бит 23 в регистре EDX установлен в 1. Пример кода для проверки:
…
mov eax, 1
cpuid
test edx, 00800000H
jnz short yes; MMX поддерживается
…
При создании функций. Использующих команды MMX допускается передача параметров через регистры MMX. В настоящее время нет соглашения для передачи параметров через регистры MMX в языке С. Поэтому функции, принимающие параметры в регистрах MMX и соответствующий вызов необходимо писать на ассемблере или делать ассемблерные вставки.
Возврат значения:
* Если значений много - записываются в структуру, а адрес структуры - в регистр EAX;
* В регистре MMX, но тогда в конце функции нельзя использовать команду EMMS, а это плохо!
Рассмотрим более подробно операции процессора при переключении в режим MMX:
* В поле экспоненты всех MMX - регистров записывается 1 во все биты (16 бит);
* В поле тегов записывается 00, что соответствует занятости всех регистров;
* Вершина стека устанавливается на физически первый регистр.
В таблице представлено влияние команд FPU/MMX на регистры
Для того, чтобы частое переключение режимов меньше сказывалось на производительности процессора рекомендуется:
* в функции (цикле) использовать команды одного типа;
* не использовать содержимое регистров, сформированное командами другого типа;
* как только команды MMX не требуются выполнить переключение режима (команда EMMS);
* перед выходом из функции, использующей FPU очищать стек FPU.
Операционные системы, сохраняющие состояние MMX/FPU
Операционные системы могут сохранять:
* полное состояние для FPU;
* сохраняет состояние по специальному запросу;
* сохраняет только частично состояние.
Вначале для всех задач предполагается, что MMX/FPU не требуется CR0.TS=1.
Если встречается команда FPU/MMX, вызывается обработчик int 7 (устройство не доступно), обработчик входит в состав OS, и выполняет:
* выделение памяти для состояния задачи;
* формируется переменная, определяющая текущий режим;
* включается требуемый режим.
Бит CR0.TS устанавливается в 0, что означает, что необходимо сохранять – восстанавливать среду.
Заметим, что само состояние сохраняется - восстанавливается пользователем с помощью команд FSAVE, FRSTOR для обоих режимов.
Таким образом, если переключаемая задача имеет CR0.TS=0, OS определяет установленный режим и состояние регистров, включает требуемый режим, если CR0.TS=1, это означает, что задача пока не использовала команд FPU/MMX, значит восстанавливать нечего.
Особенности использования режима MMX для многозадачных OS
OS по характеру обработки режима MMX можно разделить на 2 класса:
* Cooperative OS - не сохраняет состояние MMX/FPU, поэтому само приложение должно само заботиться о требуемом состоянии.
* Preemptive OS - операционная система при переключении задач сохраняет текущее состояние.
В первом случае приложение может отследить момент переключения задач и сохраняет требуемые данные, если это требуется.
Во втором случае переключение между задачами может произойти в любое время, поэтому проблему сохранения/восстановления должна решать ОS.
Отладка программ с MMX командами
При отладке программ необходимо помнить, что нет режима эмуляции как для FPU. Если установлен режим эмуляции (CR0.EM=1) и выполняется команда MMX, то формируется исключительная ситуация (int 6). Эта же ситуация возникает, если процессор не поддерживает MMX команд, а в программе встретилась эта команда.
ОСОБЕННОСТИ ПРОГРАММИРОВАНИЯ ДЛЯ MMX
Цель этого раздела - не научить программировать в режиме MMX, а дать основные особенности с целью определения необходимости использования этих возможностей процессора.
Арифметико-логический блок
Основной характеристикой процессора является его частота. Частота определяет время выполнения одного такта, которое обычно соответствует времени выполнения одной простой инструкции. Например, если частота процессора f =500 мГц (число взято для удобства вычислений) то время выполнения одного такта равно 1/f, т.е. 2 нС (1мГц = 106 гЦ, 1нС = 10-9 с).
Для увеличения быстродействия процессоров используется 2 способа: конвейерная обработка и применение нескольких исполнительных устройств.
Конвейерная обработка состоит в том, что команда делится на независимые части, которые могут исполняться различными модулями. Например, в качестве модулей могут использоваться: модуль выборки очередной команды программы, модуль анализа ее кода, модуль вычисления адресов данных, модуль исполнения, модуль записи результата. Такие 5 модулей имеют конвейеры для 486 процессора и PENTIUM. Деление на модули выполняется так, чтобы время выполнения каждого модуля было примерно одинаковым. Обозначим время выполнения всех операций для одной команды (длительность такта машины) через t., тогда, если нужно выполнить 10 команд, то потребуется не 10 t ед, а только t + 9 * t/5 ед. времени. Для общего случая, если необходимо выполнить N команд при наличии K устройств, получим формулу для вычисления требуемого времени t + (N - 1) * t/k.
Для PENTIUM 2 уже используется 12 устройств, PENTIUM 3 – 14 таких устройств.
Начиная с PENTIUM, для исполнения команды применяется несколько исполнительных устройств (ИУ), которые независимо могут выполнять команды. В этом случае фактически одновременно выполняется несколько команд и такие процессоры называют суперскалярными. Команды, которые выполняются одновременно, называют спаренными командами. Если обозначить через L количество таких устройств, то время выполнения программы, состоящей из N команд теоретически равно (t + (N - 1) * t/k)/L Почему теоретически? Дело в том, что не все команды можно спаривать, дополнительные ИУ могут выполнять только простые команды. Для Pentium используется 2 исполнительных устройства, для Pentium II и выше – 3. Таким образом, длительность вычислений тем ближе к формуле, чем больше команд спариваются, т.е. последовательность команд существенно влияет на время выполнения программы. Для Pentium последовательность исполняемых команд целиком определяется программистом, есть специальные программы, например, программа VTUNE, которая позволяет определить длительность выполнения участка программы и отследить ситуации, которые влияют на эту длительность. Для Pentium II и выше команда делится на микрокоманды и по мере готовности к выполнению складывается в буфер готовых микрокоманд. Специальный модуль извлекает эти микрокоманды в произвольном порядке, исполняет их и результат записывает в модуль перестановки, который заботится о правильном порядке записи результатов. Рассмотрим примеры. Пусть необходимо выполнить операторы:
x=5;
y=x+3;
z=4;
u=z-7;
Пусть процессор (условно) может выполнять 2 оператора параллельно и время выполнения одного оператора равно T. В этом случае параллельно можно выполнить 2 и 3 операторы и всего требуется 3T времени.
Если последовательность операторов будет такой:
x=5;
z=4;
y=x+3;
u=z-7;
то потребуется 2T времени. На Pentium требуется перестановка вручную, последующие Pentium сделают это сами, т.к. оператор y=x+3; не появится в буфере готовых команд, а последующий оператор появится. Такая ситуация называется зависимостью по результату, т.е. результат выполнения текущей команды используется в следующей команде. Заметим, что наряду с истинными зависимостями могут быть мнимые зависимости, например:
mov eax, x
mov z, eax
mov eax, y
mov t, eax
В этой последовательности команд регистр eax используется в качестве буфера для хранения данного. Начиная с Pentium 11, наряду с обычными регистрами, используются внутренние регистры. Так, для первой и третьей команд будут использованы разные внутренние регистры и они исполняются параллельно. Количество внутренних регистров равно 40, распределением этих регистров и построением таблицы соответствия занимается один из модулей ИУ процессора. Таким образом, для исполнения этих команд потребуется только 2 такта.
Прогнозирование переходов
В случае наличия команд перехода результат предварительного исполнения команд часто должен быть аннулирован, необходимо обработать другую группу команд. Вот почему рекомендуется уменьшать число команд перехода не только при программировании на любом языке программирования! Для увеличения вероятности правильной выборки команд используется блок предсказания переходов (БПП).
БПП появились, начиная с PENTIUM.
Различают два способа прогнозирования переходов: статическое и динамическое. Статическое прогнозирование выполняется, если команда перехода встретилась в программе в первый раз, динамическое – при повторном выполнении команды перехода (цикл, вызов процедуры). Для PENTIUM статическое предсказание для команд безусловного перехода – переход будет, для команд условного перехода – перехода не будет. Для старших версий PENTIUM команды условного перехода делятся на ссылки вперед и назад. Прогнозируется, что ссылка вперед будет, назад – нет. Подумайте, почему выбрана именно такая стратегия предсказания!
Примеры[2]:
s= i=0;
m1:
s+= x[i];
i++;
if (i<10)goto m1;
Для PENTIUM данный переход при первом исполнении цикла будет спрогнозирован неверно, для PENTIUM 11 верно.
Динамическое прогнозирование использует БПП. БПП включает в себя 512 строчный буфер, в каждой строке которого записывается информация об одной команде перехода. Для PENTIUM в строке записывается адрес команды перехода, адрес, куда надо перейти и 2 флага по одному биту.
Для каждой команды перехода, для которой нет еще информации в буфере, она записывается в одну строку, прогноз на переход строится в соответствии со статистическим прогнозом. Если прогнозируется наличие перехода, один из флагов устанавливается в 1. Если фактически перехода нет, этот бит сбрасывается.
При повторном выполнении команды перехода прогнозируется наличие перехода, если хотя бы один флаг установлен. Если фактически переход есть, то второй флаг устанавливается в 1, если нет – единичный флаг сбрасывается. Прогнозируется отсутствие перехода, если оба бита нулевые. Таким образом, если был переход дважды или более, то случайное отсутствие перехода не влияет на правильность прогноза. Наиболее неблагоприятный вариант – чередование наличия и отсутствия перехода.
Начиная с PENTIUM PRO, и для всех последующих процессоров, задается 4 адреса для каждой команды перехода и 4 флага, что позволяет точно предсказывать переход, если период не более 4, например, для фрагмента кода
if (a == 5) a = 7; else a = 5;
который выполняется в цикле, всегда будет предсказан верный переход, начиная с третьего выполнения. (период равен 2).
Несмотря на мощность БПП, ошибки все же возможны. Для исключения таких ошибок используется так называемое спекулятивное выполнение. Выполняются обе ветви программы. Как только адрес перехода будет вычислен, результаты для неверной ветви аннулируются. Это не совсем безобидный способ - один канал занят выполнением ненужных команд.
Регистровая память.
Регистровая память входит в структуру любого процессора. Самая быстродействующая память, но имеет ограниченный размер.
Регистры делятся на
регистры общего назначения (РОН);
сегментные регистры (СР).
управляющие регистры (УР);
отладочные регистры (ОР)
РОН предназначены для хранения исходных данных для некоторых команд (например, для команды деления содержат делимое), для хранения результатов (команда умножения формирует произведение), исходных и промежуточных данных для команд, в которых одно значение должно быть в регистре (команды пересылки, арифметические команды, …), а также используются для доступа к элементам массивов. Напоминаем о наличии внутренних регистров для каждого общего регистра. Используется 8 РОН, каждый регистр длиной 4 байта. Все регистры можно использовать как целиком, так и их части. Ниже представлены все возможные варианты использования регистра EAX:
| AH (1 байт) | AL(1 байт) | ||||||
| AX(2 байта) | |||||||
| EAX(4 байта) |
Аналогично используются регистры EBX, ECX, EDX.
Для регистров ESI, EDI, ESP, EBP применяется 2 варианта – целиком весь регистр длиной 4 байта, или его младшие 2 байта (SI, DI, SP, BP). Рекомендуем выписать все возможные регистры на Вашу «Шпаргалку»!
EAX (AX (AL, AH));
EBX (BX (BL, BH));
ECX (CX (CL, CH));
EDX (DX (DL, DH));
ESI (SI);
EDI (DI);
ESP (SP);
EBP (BP).
Буква L в обозначении регистра соответствует младшему байту (LOW), а буква H – старшему (HIGH).
CР предназначены для определения адресов. Механизм вычисления адреса зависит от режима работы. При программировании в 32-битном режиме для FLAT модели в явном виде не используются. Более подробно сегментные регистры рассмотрены в 3.4.1
УР предназначены для управления разветвлениями (регистр флагов), управления режимами работы процессора(CR0, CR1, …).
Отладочные регистры (DR0, DR1, …) используются для отладки программ.
Используются 2 режима работы процессора: реальный и защищенный. Реальный режим включается автоматически по нажатии клавиши RESET или включении питания. Однозадачный режим. Для задания адреса памяти используется выражение:
(Сегментный регистр * 16) + смещение,
где содержимое сегментного регистра и смещение – 16 битные числа. Диапазон изменения адреса 0..0xffff*0x10 + 0xffff, т.е. немного более одного мегабайта. Защищенный режим будет рассмотрен ниже.
Для работы FPU используются выделенные регистры (ST0, ST1,… ST7), для которых применяется стековая организация.
Для работы блока MMX используются регистры MM0..MM7. Для процессоров PENTIUM и PENTIUM П регистры FPU и MMX фактически используют один и тот же модуль, поэтому одновременно нельзя использовать команды MMX и FPU. Для PENTIUM Ш это ограничение снято.
Кэш память
Кэш память позволяет значительно ускорить доступ к памяти. Используется кэш память первого и второго уровней (внутренний и внешний кэш). Кэш память первого уровня расположена внутри процессора, время доступа к ней значительно быстрее, чем к другой памяти. Для 486 процессора используется общая кэш память для данных и программы (неразделенная кэш память). Размер кэш памяти 8к. Процессор выполняет команды и работает с данными из кэша. Если данное (команда) не находятся в кэш памяти, оно загружается, при этом требуется дополнительное время. Ввиду ограниченного размера кэш памяти, штрафы (промахи кэша), связанные с отсутствием данных (команд), достаточно часто возникают. Для PENTIUM используется разделенная кэш память, т.е. отдельный кэш для команд и данных. Для PENTIUM размер внутреннего кэша 16 кб. Для PENTIUM PRO внешний кеш расположен на одной системной плате с процессором. Размер кэша для PENTIUM PRO может достигать 512 килобайт. Для доступа к памяти используется 64 битная шина для PENTIUM и 128 битная для PENTIUM PRO.
Память
Как известно, команды и данные находятся в памяти. Быстродействие оперативной памяти (время доступа в среднем равно несколько десятков наносекунд) в значительной степени уступает быстродействию процессора. Для решения задачи используется:
регистровая память;
кэш (cache) память.
Блок с плавающей точкой
ИУ выполняет операции для целых чисел . Для работы с числами с плавающей точкой используется блок с плавающей точкой. Блок с плавающей точкой для PENTIUM позволяет очень эффективно выполнять операции и требует только одного такта для большинства команд. Это раскрывает возможность параллельного использования процессора и блока с плавающей точкой для организации вычислений.
Особенности MMX процессоров
MMX (MyltiMedia eXtention – мультимедиа расширение) ориентирован на эффективную обработку изображений и звука. Т.к. при решении этого класса задач приходится выполнять идентичные операции для больших массивов данных, соответствующие процессоры первоначально создавались для эффективного выполнения матричных вычислений и соответствующая аббревиатура MMX расшифровывалась как matrix-multiplication extensions. Для таких процессоров в режиме MMX добавлены специальные SIMD (Single Instraction -> Multiple Data -множество данных - одна инструкция) команды для эффективной работы. MMX процессоры, основанные на PENTIUM, имеют двойной внутренний кэш (32кб). Основная часть операций по обработке изображений переносится на процессор.
Краткая историческая справка. В связи с появлением мультимедиа технологии (в системах используются сложные графические изображения и звук) для получения хорошего качества значительно возросли требования к процессору. Т.к. быстро эти требования не могли быть выполнены, параллельно усовершенствовалась аппаратура для поддержки этих компонент (видеокарта, звуковая карта). Если раньше для получения координат каждой точки и ее цвета было обращение к процессору, то специальные акселераторы уже могли самостоятельно создавать некоторые изображения типа линии, прямоугольника, закрашивать их и т.д. Затем появились интеллектуальные акселераторы, которые могли выполнить требуемые расчеты для изображений и звука, в том числе для объемных изображений. Использование специальных аппаратных средств значительно удорожили ПК.
После появления нового класса ПК (PENTIUM, PENTIUM PRO) процессор обеспечил возможность выполнения большинства требуемых команд с нужной скоростью. Использование же 3D плат задерживает работу шины. Поэтому решается проблема создания ускоренного порта для работы с 3D акселераторами (Accelerated Graphics Port - AGP).
Перспективы разработки процессоров
Основное направление - создание 64 битных процессоров. Линия таких процессоров для архитектуры INTEL называется IA-64 или MERCED. Первый процессор этого типа должен появиться в 2000 году. Основное отличие - использование истинного параллелелизма (Explicitly Parallel Instruction Computing - EPIC). Компилятор должен находить код, который может выполняться параллельно - проблемы организации вычислений.
Поддерживается 2 дополнительных режима выполнения - режим предсказания и режим спекулятивного выполнения. Последний режим означает, что выполняются обе ветви программы до тех пор, пока не станет понятна требуемая ветвь. Результаты, полученные для другой ветви, отбрасываются.
ВВЕДЕНИЕ В АРХИТЕКТУРУ INTEL ПРОЦЕССОРОВ
Архитектура – это структура процессора с точки зрение выполняемых функций.
Классификация простейших конструкций
К простейшим конструкциям языка относятся конструкции, которые используются при построении операторов языка. Простейшие конструкции ассемблера включают в себя:
константы;
переменные;
адреса;
выражения.
Рассмотрим задание каждого типа простейших конструкций
Целые двоичные данные
Целые двоичные данные могут занимать 1, 2, 4, 6, 8 байтов в зависимости от оператора, в котором используется эта константа. Предельные значения константы зависят от ее длины. Целые константы задаются в десятичной, двоичной, восьмеричной или шестнадцатеричной системах счисления. По умолчанию используется десятичная система счисления, пример: 23, 769, -375.
Для определения констант в заданной системе счисления используются суффиксы:
B(b) - двоичная система счисления (1011b, 0111111111111B);
O(o), Q(q) - восьмеричная система счисления (1011o, 716543O, 1234 Q);
D(d) - десятичная система счисления (1011d, 23d, 769d, -375D);
H(h) - шестнадцатиричная система счисления (1011h, 23H, 0FFFFFFFFh).
Заметим, что маленькая и заглавная буквы для обозначения системы счисления равноправны. При задании шестнадцатеричной системы счисления, если число начинается с буквы (последний пример), то перед буквой должна быть цифра 0. Это необходимо для различения констант и переменных, которые начинаются с буквы.
Если большая часть констант программы должна быть задана в системе счисления, отличной от десятичной, изменяют систему счисления, принятую по умолчанию. Для этого используется директива RADIX. Общий вид директивы:
RADIX Система счисления,
где Система счисления – константа, которая всегда определяется в десятичной системе счисления. В качестве системы счисления можно задавать значения: 2, 8, 10, 16 . Директива RADIX может использоваться в программе многократно, в этом случае областью действия директивы является программный код до очередной директивы RADIX или до конца программы. Использование суффикса является более приоритетным по сравнению с использованием директивы RADIX. Например, константа 123D является десятичной независимо от предыдущей директивы RADIX. Чтобы данная константа была шестнадцатеричной, для нее необходимо записать 123DH. Целые двоичные данные можно использовать в командах, например
MOV EAX, 123DH
или директивах определения констант и данных, например:
A dd 123DH
В памяти константы записываются всегда в двоичной системе счисления.
Недостаток двоичных данных: время преобразования в двоичную систему счисления большого числа данные может быть существенным. Поэтому в системах управления базами данных, используемых, как правило, для обработки большого числа данных, использование двоичных данных ограничено.
Целые десятичные числа
Числа задаются в памяти в двоично-десятичном коде (BCD-код). Для определения таких констант в программе используется специальная директива (dt), которая задает двоично-десятичную константу в десяти байтах или используется для задания 16-ый код, например, для задания числа 23
используется форма записи 23H. Наряду с рассмотренным форматом, называемым упакованным форматом, когда для каждой десятичной цифры используется 4 бита, применяют так называемый распакованный формат числа, когда для каждой цифры используется один байт, старшие биты заполняются нулями, например, для задания числа 23 в распакованном формате можно использовать запись 0203H.
Достоинства BCD-кода:
Формирование внутреннего представления данного значительно проще, чем для двоичных данных. В этом случае требуется простая операция подстановки требуемой последовательности битов вместо цифры.
Внутреннее представление может быть получено для данных произвольной длины, для целых данных эта длина может быть равной 1, 2, 4, 6, 8 байтов.
Недостатки BCD-кода:
Для одного и того же числового значения требуется больше памяти, чем для двоичного представления. В двоичной системе счисления с помощью одного байта можно задать число от 0 до 255 включительно. В десятичном коде можно задать максимальную константу 99
для упакованного и константу 9
для распакованного форматов.
Для упакованных и распакованных данных определены не все арифметические действия, например, для данных в упакованном формате определены только операции сложения и вычитания, для распакованных данных – все операции, но только для одной цифры. После выполнения операций для BCD данных необходима корректировка результата. Для двоичных данных определены все операции для 1-4 байтовых данных. Полученные результаты корректировки не требуют.
BCD-код используется в системах управления базами данных, в которых требуется выполнить небольшое число операций для для большого набора данных. В этом случае существенный выигрыш получается за счет операций преобразования данных во внутреннее представление и наоборот.
Константы с плавающей точкой
Задаются точно так же, как на языках высокого уровня, например С. Можно использовать две формы представления:
с десятичной точкой, например 1.234, 3.12345678, 34567:
в нормальной форме, например 1.E-3, 5.3E27.
По умолчанию для константы отводится 8 байтов. Если константа задается в директиве определения константы, ее длина определяется директивой, например:
dd 5.3E27; 4 байта -float
db 5.3E27 ; 1 байт. -Ощибка
dw 5.3E27 ; 2 байта -Ощибка
dq 5.3E27 ; 8 байтов -double
dt 5.3E27 ; 10 байтов -long double
Ошибки связаны с тем, что для константы отведена недопустимая длина.
Двухбайтовая кодировка
В 1988 году фирмы APPLE и XEROX предложили 2-х символьную (широкую) кодировку. В 1991 году - комитет по разработке стандартов утвердил Стандарт на символы UNICODE. В предыдущих версиях расширенные символы кодировались так, что младший байт был равен 0, это было признаком двух байтовой кодировки (например, кодировка функциональных клавиш F1 –F12). В UNICODE каждый символ кодируется 2 байтами, поэтому не надо анализировать первый байт, чтобы узнать назначение второго - сейчас в стандарт UNICODE входит »34000 символов. Для программистов оставлено »6000 кодов. В табл. 3.1 представлены коды символов в UNICODE.
Таблица 3.1 Коды символов для UNICODE
| 16-битный код | Символы | ||
| 0000 - 007F | ASCII | ||
| 0080 – 00FF | Символы LATIN 1 | ||
| 0100 - 017F | Европейские латинские | ||
| 0180 – 01FF | Расширенные латинские | ||
| 0250 – 02AF | Стандартные фонетические | ||
| 02b0 – 02ff | Модифицированные литеры | ||
| 0300 - 036f | Общие диакритические знаки | ||
| 0400 – 04ff | Греческий | ||
| 0530 - 058f | Кириллица | ||
| 0590 – 05ff | Армянский | ||
| 0600 – 06ff | Еврейский | ||
| 0900 - 097f | Девангари |
Достоинства использования UNICODE:
возможность создания многоязычных документов;
один exe или dll файл для разных языков;
увеличивается эффективность прикладных программ.
WINDOWS NT полностью построена на основе UNICODE. Если функции передается обычная строка, она сначала преобразуется в строку типа UNICODE (автоматически). Так как преобразование требует дополнительной памяти и времени, лучше сразу работать со строками типа UNICODE. В WINDOWS 95 не заложена работа с UNICODE, вся внутренняя работа основана на одно символьной кодировке, поэтому все 2-х символьные строки преобразуются в одно символьные.
В ассемблере нет специального обозначения[3]
для расширенной кодировки, поэтому мы используем для задания таких строк данные типа DW, что соответствует 2-х байтовым данным. Так строка «Hello» при двухбайтовой кодировке имеет вид:
Msg dw “H”, “e”, “l”, “l”,”o”
Однобайтовая кодировка
Символ задается в кавычках (одинарных или двойных), например:
SymbolD DB ‘D’
SymbolA DB ‘A’
…
MOV AL, ‘D’; Символ D записывается в регистр AL.
MOV BL, ”A”; Символ A записывается в регистр BL.
MOV BH, SymbolA; Символ A записывается в регистр BL.
Если необходимо задать строку, состоящую из нескольких символов, используется запись вида:
Msg db ‘Hello, World’
Вместо одинарных кавычек можно использовать двойные. Если необходимо задать строку с нулевым завершителем, строка имеет вид:
Msg db ‘Hello, World’, 0
Для символов можно задать их код, например:
Msg db ‘Hello’, 13, 10, ‘World’, 13, 10, 0
Символы 13, 10 обеспечивают переход в начало очередной строки.
Символьные и строковые константы
Под символ отводится один или два байта. Один байт выделяется при использовании ASCII кодировки, два байта – UNICODE. Рассмотри сначала кодировку данных однобайтную, а затем двух байтную
Константы
Константы делятся на арифметические и строчные. К арифметическим константам относят целые числа и числа с плавающей точкой. К строчным данным относятся данные, включающие в себя один или несколько символов.
Идентификаторы
Используются для обозначения:
Переменных;
Констант;
Функций.
Идентификатором является запись, которая начинается с буквы или символа подчеркивания, в качестве остальных символов могут использоваться латинские буквы, цифры или символ подчеркивания. Количество символов идентификатора – один и больше. Максимальное количество символов не ограничено. Ограничено количество значащих символов (по умолчанию 255). Количество значащих символов идентификатора можно изменить с помощью флага mv#, в котором записывается максимальное количество символов идентификатора. Минимальное количество значащих символов равно 15.
Внутреннее представление адреса для реального режима
Адрес определяется двумя компонентами: сегмент и смещение. Такой адрес называется двух компонентным. Двух компонентный адрес используется для обеспечения возможности перемещения программы в памяти. Сегмент определяет адрес начала программы в памяти, а смещение задает ее смещение относительно начального адреса. Для реального режима сегмент и смещение задаются 16 - разрядными числами, текущий адрес определяется по формуле:
Сегмент * 16 + Смещение.
Заметим, что изменение содержимого сегментного компонента изменяет адрес. Сегментный компонент записывается в сегментный регистр. По умолчанию для задания адреса кода используется регистр CS, для задания адреса данных - регистр DS, а адреса стека - SS. Для обозначения данных можно также использовать сегментные регистры ES, GS, FS.
Определим диапазон изменения адреса для реального режима. Максимально возможный адрес 0FFFFH. Максимально возможное смещение равно 0FFFFH. Таким образом, максимально возможный адрес равен
0FFFF0H + 00FFFFH = 10FFEFH.
При разработке первых процессоров казалось, что адресного пространства до 0FFFFFH будет вполне достаточно, это адресное пространство соответствует 1 мГб, поэтому говорят, что в реальном режиме адресуется 1 мГб памяти. Более того, адресное пространство, начиная с адреса 0FE00H было выделено под BIOS (Basic Input / Output System), содержащее константы и стандартные функции для работы с внешними устройствами. BIOS позволяет сделать независимым программирование для внешних устройств разных производителей. Адресное пространство 0A0000H .. 0C0000H выделено для видео памяти, т.е. для хранения содержимого на мониторе, а начиная с 0C0000H для дополнительных модулей BIOS. Таким образом, для программ остается адресное пространство 0 .. 9FFFFH. Размер этого пространства 10 * 65535 = 655350 байт, т.е. 640 Кб. Сегментный компонент всегда записывается в сегментный регистр.
Недостатки адресации в реальном режиме:
не позволяют работать со всей оперативной памятью для современных компьютеров;
максимальный размер сегмента 64 кБ;
нет защиты памяти, заданной программой и данными от других программ, в том числе сама операционная система реального режима не защищена от программ пользователя.
Внутреннее представление адреса для защищенного режима
При определении внутреннего представления адреса исходили из необходимости совместимости задания адреса для защищенного и реального режимов, т.е. адрес должен быть 2-х компонентным и задаваться с помощью сегментного компонента и смещения, причем, для задания сегментного компонента должен использоваться сегментный регистр. С учетом этих требований, первый компонент задает не адрес начала сегмента, а номер в таблице, где находится этот адрес. Поэтому регистры DS, CS, ... теперь называются не сегментными регистрами, а селекторами. Адрес начала и смещение 32-битные, поэтому диапазон изменения адреса 232 , что соответствует 4 ГБ памяти. Для вычисления адреса используется следующий алгоритм.
По сегментному регистру определяют таблицу, в которой определен адрес начала сегмента, и номер строки в таблице для определения этого адреса. Таблица может быть глобальной и локальной. Глобальная таблица содержит коды и данные, общие для всех программ, в том числе, для операционной системы. К таким кодам относятся, например, коды для библиотеки графического интерфейса. Локальная таблица содержит коды и данные только для одной программы. Благодаря этому обеспечивается разделение адресного пространства между программами.
В заданной строке таблицы для адреса определяется не только его начальное значение, но и тип сегмента (коды, данные, стек), его размер, права использования, что обеспечивает защиту от неправильного использования области памяти.
Полученный начальный адрес складывают с адресом, заданным в качестве смещения в команде. Получают исполнительный адрес[4]
Внешнее представление адресов.
Ниже рассмотрено внешнее представление адреса для защищенного режима. Особенности задания адресов в реальном режиме можно изучить по [1].
Используются следующие способы задания адреса: относительная адресация, базисная адресация, индексная адресация, базисно - индексная адресация, относительная базисно - индексная адресация[5].
Относительная адресация. Адрес задается переменной или записью вида: Переменная ±Смещение. Определяет адрес с помощью смещения заданной переменной относительно начала сегмента, внутри которого она определена. Смещение должно быть задано как константное выражение, т.е выражение, значение которого может быть вычислено на этапе компиляции. Может отсутствовать.
Пример:
X DD 3, 2
...
MOV EAX, [X]
MOV EBX, [X+4]
Данный способ используется для задания адреса простой переменной или элемента массива с заданным номером.
Базисная адресация. Общий вид адреса: [Регистр + Смещение]. Смещение задается так же, как для предыдущей адресации. В качестве регистра можно использовать любой 32-битный регистр общего назначения, куда предварительно записывается адрес начала. Для задания адреса можно использовать операцию OFFSET, которая задается перед переменной и означает определить адрес переменной. Другие способы определения адреса в программе будут рассмотрены ниже. Пример:
MOV EAX, OFFSET X; EAX = &X
MOV EBX, [EAX]; EBX = X
Если в качестве регистра используется регистр ESP или EBP, предполагается, что данные используются из сегмента стека, иначе из сегмента данных, например:
MOV EBX, [EBP]; Данные из стека
Если необходимо переопределить расположение данных, используется переопределение сегмента, например, пусть данные определены в сегменте данных, для доступа к таким данным через регистр EBP пишут:
MOV EBX, [DS: EBP];
Способ адресации используется для задания адреса произвольного элемента массива. Регистр задает адрес заданного элемента массива. Например, для обнуления элемента массива с данными длиной 1 байт, используются команды:
MOV EAX, OFFSET X; EAX = &X[0]
ADD EAX, [I] EAX = &X[I]
MOV BL, 0 BL = 0
MOV [EAX], BL; X[I] = 0
Индексная адресация. Адрес задается в виде: [Переменная + Регистр * Масштаб + Смещение], где:
переменная определяет имя массива;
регистр, называемый индексным, (может быть любой 32 - битный регистр общего назначения) задает номер используемого элемента массива;
масштаб - позволяет учесть длину элемента массива. Принимает значения: 1, 2, 4, 8 (может отсутствовать, в этом случае считается равным 1);
смещение - как для предыдущих способов адресации (может отсутствовать).
Пример:
MOV ESI, I
MOV EAX, [X+ESI * 4]; EAX = x[I]
Используется для адресации элементов массива стандартной длины. Наиболее наглядный способ задания элемента одномерного массива, задание аналогично его заданию на языке высокого уровня.
Базисно - индексная адресация. Адрес задается в виде: [Регистр1+ Регистр2 * Масштаб + Смещение], где
Регистр1 - базисный регистр - содержит адрес начала массива (как в базисной адресации);
Регистр2 - индексный регистр (как в индексной адресации)
Масштаб и смещение - как в индексной адресации
Смещение - как для предыдущей адресации (может отсутствовать).
Используется для адресации 2-х мерных массивов.
Пример будет рассмотрен ниже
Относительная базисно- индексная адресация
- адрес задается в виде :[Переменная + Регистр1+ Регистр2 * Масштаб + Смещение], где компоненты определены в соответствующих способах адресации. Фактически для вычисления адреса используются 5 компонентов.
Переменная, Регистр1, Регистр2, Масштаб, Смещение,
при трансляции адрес переменной и смещение заменяются одной константой, т.е. с точки зрения внутреннего представления последние два способа эквивалентны и используются для работы с двух мерным массивом.
Адреса
Рассмотренные выше команды показывают, что главным элементом машинной команды и соответствующей ей команды на ассемблере является адрес. Рассмотрим сначала задание адреса в машинных командах (внутреннее представление адреса), а затем в ассемблер программах (внешнее представление адреса).
Выражения
Выражения строятся из:
констант;
переменных;
знаков арифметических операций (+, -, *, /, MOD[6]);
знаков отношений (LT[7]
- меньше, LE – меньше равно, EQ - равно, NE – не равно, GT - больше, GE-больше равно);
операций побитовой обработки (AND- логическое умножение, OR- логическое сложение, NOT - инверсия, XOR-сложение по модулю 2);
операций сдвига (SHL, SHR);
дополнительных операций, например, size – размер данного в байтах; length – коэффициент повторения для первого элемента списка, type – значение определяется типом данного и равно длине данного в байтах;
зарезервированных символов и идентификаторов.
В табл. 3.2 представлены зарезервированные символы и слова, а также значения, соответствующие этим словам
Таблица 3.2. Значения для зарезервированных символов и слов
| Слово (символ) | Значение | ||
| $ | Текущее значение адреса | ||
| NOTHING | 0 | ||
| ? | 0 | ||
| UNKNOWN | 0 | ||
| BYTE | 1 | ||
| WORD | 2 | ||
| DWORD | 4 | ||
| PWORD | 6 | ||
| FWORD | 6 | ||
| QWORD | 8 | ||
| TBYTE | 10 | ||
| PROC | 4 | ||
| CODEPTR | 4 | ||
| DATAPTR | 4 |
Правила вычисления выражений:
Все выражения вычисляются на этапе трансляции.
Если в выражении встречается имя переменной, подставляется ее адрес, например в участке программы
X DD 5
DD 3
…
MOV EAX, [X+4]
в регистр EAX запишется значение, которое находится по адресу X+4,
т.е. число 3.
При вычислении значения выражения учитывается только целая часть результата, дробная часть отбрасывается.
Вычисления проводятся с точностью до 232, если промежуточный или окончательный результат сложения или вычитания превосходит это значение, он усекается (отбрасываются старшие цифры результата), при этом формируется предупреждение. Если результат умножения выходит за пределы, формируется ошибка, связанная с выходом за пределы.
¬ +ёыш фхышЄхы№ Ёртхэ 0, ЇюЁьшЁєхЄё ю°шсър л-хыхэшх эр 0¬.
¬ LёЄшэр ш ыюц№ ъюфшЁєхЄё ёююЄтхЄёЄтхээю 0xFFFFFFFF ш 0.
=шцх яЁхфёЄртыхэ яЁшьхЁ ЇЁруьхэЄр яЁюуЁрььv ё тvЁрцхэш ьш. +яЁхфхышЄх Ёхчєы№ЄрЄ ЄЁрэёы Ўшша тvЁрцхэш т ърцфюь ёыєўрх ш яЁютхЁ№Єх яюыєўхээvх Ёхчєы№ЄрЄv яю ышёЄшэує яЁюуЁрььv.
ideal
p586
extrn ExitProcess:proc
model flat
dataseg
x ddааааааа 1, 2 dup (1),
ааааааааааа ddаааааааааа 0.53, 53., 1.57
аа ddааааааа 1.E-3
аа ddааааааа 5.3E27
аа dbааааааа 5.3E27
аа dwаааааа 5.3E27
аа dqааааааа 5.3E27
аа dtаааааааа 5.3E27
codeseg
begin:
a =аааааааа 1 SHRа 5
b =аааааааа -1 SHL 5
c =а -1 SHL 3
movааааааа eax, size x
movааааааа ebx, length x
movааааааа edx, [ecx+4]
movааааааа esi,7/4
movааааааа edi, 0ffffffffh+2
movааааааа edi, 0ffffffffh/0ffffffffh
movааааааа edi, 1/0
movааааааа edi, 1 lt 0
movааааааа edi, 1 gt 0
callаааааааа ExitProcess
Классификация операторов
Операторы ассемблера делятся на директивы и команды. Директива определяет информацию компилятору об используемых режимах, расположении данных и т.д. Выше были рассмотрены директивы MODEL, директивы выделения памяти. Команды определяют команды машинного языка.
Формат оператора:

Имя используется для директив, метка для команды. В качестве операндов могут быть:
обозначения констант;
константы;
регистры;
адреса памяти.
Пример директивы:
MODEL FLAT
Здесь MODEL - код операции, FLAT - операнд.
Пример команды:
M1: MOV EAX, 5; Это комментарий
В этой команде M1 – метка, MOV – код операции, EAX, 5 – операнды.
Директивы и команды изучим постепенно. Сейчас рассмотрим директивы определения констант и выделения памяти.
Директива EQU
Директива EQU позволяет задать любую последовательность символов, которая подставляется в программу вместо имени.
Общий вид директивы EQU:
Имя EQU Символы
Значение, заданное для имени (поле символов), не может быть переопределено в программе.
Примеры использования директив:
MaxSize EQU 100
…
MOV EAX, MaxSize+2
Заметим, что команда
MOV EAX, MaxSize+2
эквивалентна команде
MOV EAX, 100+2
В поле символов могут быть заданы имена, которые определены в другой директиве EQU. Если имя определено в директиве EQU после использования, требуется транслировать программу, используя несколько просмотров (проходов). Количество проходов при компиляции определяется ключом /mчисло просмотров, например, для трансляции фрагмента программы
A EQU B
B EQU C
C EQU 3
MOV EAX, A
требуется 2 просмотра, т.е. в командной строке необходимо задать ключ /m2
Директива =
Общий вид директивы =:
Имя = выражение
Значение, заданное для имени, вычисляется на этапе трансляции. Может изменяться в программе. В выражении правой части можно использовать переменные, определенные с помощью =, в том числе, и предыдущее значение переменной левой части. Правила задания выражений в директиве = определены выше (подраздел 3.5).
Примеры команд с константами:
X = 5
MaxSize = 100
…
MOV EAX, X
MaxSize = MaxSize - 10
MOV EBX, MaxSize+2
Заметим, что последняя команда эквивалентна команде:
MOV EBX, 92.
Для четкого понимания различия между директивами определения констант еще раз сравните примеры использования этих директив!
Директивы для определения констант
Для обозначения констант используется 2 типа директив EQU и =.
Директивы выделения памяти
Код директивы определяет тип данного, для которого выделяется память.
В табл. 4.1 определены директивы для выделения памяти и соответствующие типы данных для языка С.
Таблица 4.1. Директивы выделения памяти
| Код директивы | Размер данного (байт) | Тип соответствующего данного в языке С | |||
| DB | 1 | char | |||
| DW | 2 | short | |||
| DD | 4 | int, unsigned, float | |||
| DF, DP | 6 | - | |||
| DQ | 8 | double, int64 | |||
| DT | 10 | long double |
При выделении памяти допускается инициализировать выделенную область и не инициализировать ее. Для правильной работы программы первая переменная в сегменте данных DATASEG должна быть инициализирована. Если переменная инициализируется, в поле операнда задается ее значение, если не инициализируется, ставится знак ? вместо соответствующего значения. Одной переменной может соответствовать несколько значений (массив). Среди элементов массива могут быть инициализированные и неинициализированные элементы. Под адрес всегда отводится 4 байта (данное типа DD). Для инициализации адреса достаточно в поле операнда задать имя данного, для которого определяется адрес.
Примеры директив.
Оттранслировать директивы с языка С++ на ассемблер, если память выделяется в сегменте данных:
char c1 = ‘a’, c2, c3 = «bcde»;
short s1 = 2, s2[] = {1,2,3};
unsigned short s2 = 60000;
int i1[]={7, 3, -2, 4};
float f1 = 5, f2;
double d1 = 5, d2, d3 = 7e-20;
long double ld1 = 3, ld2[5];
int *p = i1, *q;
DATASEG
;char c1 = ‘a’, c2, c3 = «bcd»;
c1 db ‘a’
c2 db ?
c3 db «bcd», 0
;short s1 = 2, s2[] = {1,2,3};
s1 dw 2
s2 dw 1, 2, 3
;unsigned short s3 = 60000;
s3 dw 60000
; int i1[]={7, 3, -2, 4};
i1 dd 7, 3, -2, 4
;float f1 = 5, f2;
f1 dd 5.;
f2 dd ?
;double d1 = 5, d2, d3 = 7e-20;
d1 dq 5.;
d2 dq ?
d3 dq 7e-20
; long double ld1 = 3, ld2[5];
ld1 dt 3.;
ld2 dt 5 dup (?)
;int *p = i1, *q;
p dd i1
q dd ?
Обратите внимание на задание адресов!
Заметим, что директивы для выделения памяти под c1 .. d3 лучше поменять местами, так как в этом случае доступ к переменным выполняется быстрее с учетом выравнивания адресов.
Классификация команд
По выполняемым действиям команды делятся на:
команды пересылки и обмена;
арифметические команды;
команды для организации разветвлений и циклов и для обработки массивов;
команды для работы с битами;
команды для работы с функциями;
команды управления процессором и определения информации о нем;
команды для работы с FPU;
команды для работы с MMX,
Команда MOV
Общий вид команды:
[Метка:] MOV Операнд1, Операнд2 [; Комментарий]
Требования к операндам:
длины операндов должны быть одинаковы и равны (1, 2, 4) байтов;
в качестве операндов можно использовать регистры всех типов (использование управляющих и отладочных регистров не рассматривается в этом курсе), адреса памяти и непосредственные данные (для исходных данных). Разрешенные и неразрешенные типы операндов команды MOV представлены в табл. 4.2
Таблица 4.2. Разрешенные и неразрешенные операнды команды MOV
| 2 операнд | |||||||||
| 1 операнд | РОН | СР | Адрес памяти | Непо-средственное данное | |||||
| РОН | + | + | + | + | |||||
| СР | + | - | + | - | |||||
| Адрес памяти | + | + | - | + |
Как видно из таблицы, если первый операнд регистр общего назначения, то второй операнд может быть любым, для сегментного регистра запрещено использование другого сегментного регистра или непосредственного данного, запрещено использование двух адресов памяти.
Примеры команд:
Реализовать на ассемблере операторы присваивания для переменных и массивов, определенных выше.
c2 = c1; c3[1] = c1;
swap (s2[0], s2[2]); s2[1] = s3;
i1[3] = i1[0]
i1[1] = i1[2];
f2 = f1;
d2 = d1;
q = &i1[3]; *p = *q;
Реализация на ассемблере
;c2 = c1; c3[1] = c1;
MOV AL,[ c1]
MOV [c2], AL
MOV [c3+1], AL
;swap (s2[0],s2[2]); s2[1] = s3;
MOV AX, [s2+4]
MOV BX, [s2]
MOV [s2+4], BX
MOV [s2], AX
MOV AX, [s3]
MOV [s2+2], AX
;i1[3] = i1[0]; i1[1] = i1[2];
MOV EAX, [i1]
MOV [i1+12], EAX
MOV EAX, [i1+8]
MOV [i1+4], EAX
;f2 = f1;
MOV EAX, [f1]
MOV [f2], EAX
;d2 = d1;
MOV EAX, [d1]
MOV [d2], EAX
MOV EAX, [d1+4]
MOV [d2+4], EAX
Использование данных разной длины
Пусть необходимо оттранслировать операторы присваивания:
с2 = s3;
i1[0] = c2;
Для обращения к данным разной длины необходимо преобразование их типов. Для преобразования типов используется преобразователь вида:
<Тип> PTR, где тип : BYTE PTR, WORD PTR, DWORD PTR, PWORD PTR, FWORD PTR, QWORD PTR, TBYTE PTR. Для режима IDEAL вместо записи вида <Тип> PTR можно использовать только тип, например BYTE. Преобразование типа можно использовать только для уменьшения длины. Использование преобразователя типа для увеличения длины приведет к неправильному обращению к данному, т.к. фактически используются последовательные области памяти.
Пример использования преобразования типа в командах пересылки:
; с2 = s3;
mov AL, [BYTE PTR s3]
mov [c2], AL
; i1[0] = c2;
mov eax, 0
mov al, [c2]
mov [i1], eax
Реализация последнего оператора корректна только в том случае, если правая часть - число положительное. Для отрицательного числа необходимо другое начальное значение eax.
Для расширения знаковых и беззнаковых чисел используются команды:
MOVZX – расширение беззнакового числа;
MOVSX – расширение знакового числа.
В этих командах первый операнд - регистр R16 или R32. Второй операнд регистр или память длиной 8, 16 байтов. Дополнение выполняется нулями (MOVZX), или знаковым разрядом (MOVSX).
С помощью этих команд
; i1[0] = c2;
movzx eax, [c2]
mov [i1], eax
Команды обмена
Для обмена данными можно использовать 4 команды MOV или специальные команды.
Команда XCHG
XCHG R8|R16|R32, R8|R16|R32|M8|M16|M32
Запись означает, что в качестве первого операнда можно использовать регистры общего назначения длиной байт (R8), слово (R16) или двойное слово (R32), в качестве второго – те же регистры или адреса памяти длиной байт, слово или двойное слово (M8, M16, M32).
Пример 1. Используя команду XCHG, реализовать оператор swap (s2[0], s2[2]) для данных длиной 2 байта
;swap (s2[0], s2[2])
mov ax, [s2]
xchg ax, [s2+4]
mov [s2], ax
Команда позволяет вместо двух общих регистров использовать только один, вместо 4-х команд используются 3, но команда xchg не входит в основную группу команд, которая оптимизирована и не может быть спарена с другими командами, поэтому использование команд MOV
по времени выполнения более эффективно.
Пример 2. Пусть необходимо поменять местами байты числа длиной 4 байта
X dd 11223344h
Y dd ?
…
mov eax, [X]
mov [Y], eax
mov al, [BYTE Y]
mov ah, [BYTE Y +3]
mov [BYTE Y +3], al
mov [BYTE Y ], ah
mov al, [BYTE Y+1]
mov ah, [BYTE Y +2]
mov [BYTE Y +2], al
mov [BYTE Y+1], ah
Команда BSWAP
BSWAP R32
Решение предыдущей задачи с помощью команды BSWAP
MOV EAX, [X]
BSWAP EAX
MOV [Y], EAX
Команды пересылки для работы с адресами
Так как адреса - это целые длиной 32 бита (модель flat), для работы с адресами можно использовать обычную команду mov.
Пример. Реализовать операторы
Int i1[4], *p=i1, *q;
q = &i1[3]; *p = *q;
i1 dd 4 dup (?)
p dd i1
q dd ?
…
; q = &i1[3]; *p = *q;
mov eax, [p]; &i1[0]
add eax, 3*4; &i1[3]
mov [q],eax; q=&i1[3]
mov ebx, [eax]; *q
mov ecx, [p]
mov [ecx], ebx; *p = *q;
В этом примере используются области памяти для хранения адресов. Для формирования адреса без использования областей памяти можно использовать операцию offset и команду mov.
Например, команда
mov eax, offset x
записывает адрес переменной x
в регистр eax.
По второму адресу можно задать адрес данного с использованием только относительной адресации. Для других способов адресации операция offset не используется.
Для этого используется специальная команда:
lea <оп1>, <оп2>.
В качестве <оп1>
используется регистр 32-битный или 16 битный в зависимости от типа приложения. В качестве <оп2>
используется адрес памяти, для задания которого можно применять любой способ адресации.
Пример 1. Команды
mov eax, offset x
lea eax, [x]
эквивалентны по выполняемым действиям, но первая команда более эффективна.
Пример 2. Записать адрес третьего элемента массива x , если длина элемента – 4 байта:
Lea eax, [x+12]
Использование команды lea для арифметических вычислений
Команду lea можно использовать для умножения на некоторые целочисленные константы, это позволяет уменьшить требуемое время выполнения операции, т.к. команда умножения требует больше времени, чем команда lea.
Примеры
lea eax, [eax+eax]; eax = eax *2
lea eax, [eax+eax*2]; eax = eax *3
lea eax, [eax*4]; eax = eax *4
lea eax, [eax+eax*4]; eax = eax *5
lea eax, [eax+eax*8]; eax = eax *9
Напоминаем, что масштабный множитель можно задавать только для одного регистра. Значение масштабного множителя {1,2,4,8}.
Использование команд mov
Пример. Записать в стек число 5.
При записи в стек должны выполняться действия:
esp--;
*esp =5;
sub esp, 4
mov [dword esp], 5
Преобразование типа требуется, так как при косвенной адресации неизвестен тип данного.
Использование специальных команд
Используются команды:
push <Оп>- Запись в стек <Оп>. В качестве операнда могут быть регистры общего назначения с длиной, равной длине элемента стека, константа или адрес памяти. Команда выполняет действия: ESP--; *ESP = Оп;
pop <Оп>- Извлечение из стека. В качестве операнда могут быть регистры общего назначения с длиной, равной длине элемента стека или адрес памяти. Команда выполняет действия: Оп = *ESP++
pushad - Запись в стек содержимого всех общих регистров
popad - Извлечение из стека содержимого всех общих регистров.
Пример. Поменять местами значения x, y.
Push [x]
push [y]
pop [x]
pop [y]
Особенности команд пересылки для работы со стеком
Длина элемента стека зависит от типа приложения. Для 32-битного приложения длина равна 4 байта, для 16-битного -2 байта. Элемент стека выравнивается на границу длины.
Для работы со стеком можно использовать:
обычные команды mov и специальные команды.
Классификация арифметических команд
По типу используемых данных арифметические команды делятся на команды для работы:
с целыми числами;
с числами с плавающей точкой.
Команды для работы с целыми числами делятся на команды для работы с двоичными и двоично - десятичными данными. Последний тип данных используется в системах управления базами данных и в данном курсе не рассматриваются.
По длине используемых данных команды для целых чисел делятся на:
команды для работы с двойными словами (4 байта), словами (2 байта), байтами.
Ниже будут рассмотрены команды для работы с целыми двоичными числами.
Правила использования арифметических команд
1. Для целых операндов результат всегда получается целого типа, даже для операции деления - формируется целая часть частного и остаток со знаком делимого
2. Длины операндов должны совпадать, длина результата равна длине операндов.
Исключение 1. При выполнении операции умножения произведение может быть в два раза длиннее сомножителей
Исключение 2. При выполнении операции деления делимое должно быть в два раза длиннее частного. Остаток имеет длину делителя.
Основные арифметические команды
Основные арифметические команды представлены в табл. 5.1
Таблица 5.1. Основные арифметические команды
| Назначение | Общий вид | Комментарий | |||
| Сложение | add <Оп1>, <Оп2> xadd <Оп1>, <Оп2> | Оп1 = Оп1 + Оп2 Оп1«Оп2, Оп1 = Оп1 + Оп2 | |||
| Вычитание | sub <Оп1>, <Оп2> | Оп1 = Оп1 - Оп2 | |||
| Умножение беззнаковое | mul < Оп > | ![]() | |||
| Умножение знаковое | imul < Оп > | ![]() | |||
| Умножение знаковое 2-х операндное | imul <Оп1>, <Оп2> | Оп1 = Оп1 * Оп2 | |||
| Умножение знаковое 3-х операндное | imul <Оп1>, <Оп2>, константа | Оп1 = Оп2 * константа | |||
| Деление беззнаковое Деление знаковое | div <Оп> idiv <Оп> | ![]() |
Во всех командах не допускается два адреса памяти.
При выполнении сложения с помощью команды xadd второй операнд должен быть регистром.
Команды умножения с двумя (тремя ) операндами есть только для знаковых умножений (умножение с учетом знака). Эти команды формируют произведение длиной сомножителей, поэтому их нецелесообразно использовать в случае возможного переполнения.
Деление выполняется нацело, дробная часть результата отбрасывается. Одновременно с частным формируется остаток от деления. Если при выполнении деления частное не помещается в отведенное для него поле, возникает особая ситуация “Деление на ноль”. Команда деления может требовать специальной подготовки для записи делимого. Подготовка состоит в расширении делимого знаковым разрядом для чисел со знаком и обнулении старшей части делимого для чисел без знака. Для этих целей можно использовать команды пересылки или специальные команды. Команды пересылки (MOV) рассмотрены выше
Специальные команды
Так как делимое всегда должно быть расположено в фиксированных регистрах, которые зависят от его длины, команды расширения делимого не имеют операндов. Код команды расширения определяется тем, что и во что преобразовывается. Команды для подготовки деления заданы в табл. 5.2
Таблица 5.2 Команды для подготовки деления
| Выполняемое преобразование | Код | Исходное данное | Результат | ||||
| Байт в слово | CBW | AL | AX | ||||
| Слово в двойное слово | CWD | AX | DX, AX | ||||
| двойное слово в два двойных слова | CDQ | EAX | EDX, EAX | ||||
| Слово в двойное слово | CWDE | AX | EAX |
Пример использования арифметических команд.
Пример 5.1 Пусть заданы значения X, Y. Выполнить операции +, - , *, / и вычисления остатка для данных типа DWORD
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
x dd 7fffffffh
y dd 7fffffffh
z dd ?
zl dd ?
zh dd ?
codeseg
begin:
; z=x+y
mov eax, [x]
add eax, [y]
mov [z], eax
; z=y+x
mov eax, [x]
mov ebx, [y]
xadd eax, ebx
mov [z], eax
; z=x-y
mov eax, [x]
sub eax, [y]
mov [z], eax
; z=x*y (x, y - unsigned)
mov eax, [x]
mul [y]
mov [zl], eax
mov [zh], edx
; z=x*y (x, y -int)
mov ebx, [x]
imul ebx, [y]
mov [zl], ebx
; z=x*5 (1 способ)
mov ebx, [x]
imul ebx, 5;
mov [zl], ebx
; z=x*5 (1 способ)
mov ebx, [x]
imul eax, ebx, 5
mov [zl], eax
;zl=x/y; zh=x%y; x, y - unsigned
mov eax, [x]
sub edx, edx
div [y]
mov [zl], eax
mov [zh], edx
;zl=x/y; zh=x%y; x, y -int
mov eax, [x]
cdq
idiv [y]
mov [zl], eax
mov [zh], edx
call ExitProcess
end begin
Для обеспечения максимально быстрого выполнения умножения на целое число используется макрос FASTIMUL, в котором умножение заменяется операциями сложения и сдвига, например, умножение на 5 рассматривается как умножение на 4 (сдвиг на 2 бита влево и сложение первоначального значения). Требуемые операции сдвига и сложения формирует компилятор по сомножителю. Общий вид макрокоманды:
FASTIMUL оп1, оп2, константа; оп2 * константа® оп1
Организация вычислений с многократной точностью
Будем называть число с многократной точностью, если для его задания требуется более одного машинного слова. Такие числа широко используются при решении различных практических задач. Так, криптография на открытых ключах построена на числах с многократной точностью, длина которых 1024 и более бит. Для 32- битных процессоров число с многократной точностью содержит более 32 бит.
В основе вычислений с многократной точностью лежат вычисления «в столбик». В этом случае на каждом шаге требуется выполнять операции с однократной точностью. Для учета возможных переносов при сложении и заемов при вычитании используются специальные команды, представленные в табл. 5.3
Таблица 5.3 Команды для вычислений с многократной точностью
| Назначение | Общий вид | Комментарий | |||
| Сложение | adc <Оп1>, <Оп2> | Оп1 = Оп1 + Оп2 + бит переноса | |||
| Вычитание | sbb <Оп1>, <Оп2> | Оп1 = Оп1 - Оп2- бит переноса |
При переполнении (заеме) формируется бит переноса, который учитывается при выполнении операций сложения и вычитания для очередной порции числа (цифры).
Рассмотрим примеры вычислений с многократной точностью.
Пример1. Составить программу для сложения двух 64 - разрядных чисел[8]
dataseg
x dd 12345678h, 9abcdefah
y dd 0ffffffffh, 0ffffffffh
z dd 3 dup (?)
...
Codeseg
..
; Сложение младших «цифр» числа.
mov eax, [x]
add eax, [y]
mov [z], eax
; Сложение старших «цифр» числа.
mov eax, [x+4]
adc eax, [y+4]
mov [z+4], eax
;Учет переноса
mov [z+8], 0
adc [z+8], 0
Очевидно, что для вычитания таких чисел необходимо команы ADD, ADC заменить командами SUB, SBB.
Пример 2. Составить программу для умножения двух 64-разрядный чисел x, y.
Очевидно, что
x = x1* 232 + x0;
y = y1* 232 + y0;
где (x1, x0) – старшая и младшая цифры числа x;
где (y1, y0) – старшая и младшая цифры числа y;
В результате умножения x * y получим:
x * y = (x1* 232
+ x0) * (y1* 232 + y0) = x0 * y0 + (x1 * y0 + x0 *y1) 232 + x1 * y1.
¦ЁюуЁрььр, яЁштхфхээр эшцх, тvўшёы хЄ чэрўхэшх лЎшЇЁ¬ яЁюшчтхфхэш ё єўхЄюь яхЁхяюыэхэш эр ърцфюь °рух.
dataseg
xааааааааа ddааааааа 12345678h, 9abcdefah
yааааааааа ddааааааа 0ffffffffh, 0ffffffffh
zаааааааааа ddааааааа 4 dup (?)
...
Codeseg
...
subаааааа eax, eax
movаааа [z+8], eax
movаааа [z+12], eax
; ¦ырф°р ЎшЇЁр Ёхчєы№ЄрЄр z0
movаааа eax, [x]
mulааааа [y]
movаааа [z], eax
movаааа z[4], edx
; ¦эрўхэшх z1
movаааа eax, [x+4]
mulааааа [y]
addааааа [z+4], eax
adcааааа [z+8], edx
movаааа eax, [x]
mulааааа [y+4]
addааааа [z+4], eax
adcааааа [z+8], edx
adcааааа [z+12], 0
; ¦эрўхэш z2 ш z3
movаааа eax, [x+4]
mulааааа [y+4]
addааааа [z+8], eax
adcааааа [z+12], edx
¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы фхыхэш 64 сшЄэюую ўшёыр эр 32-сшЄэюх.
xааааааааа ddааааааа ..., Е
yааааааааа ddааааааа Е
zаааааааааа ddааааааа ?, ?
movаааа edx, 0
movаааа eax, [x+4]
divаааааа [y]
movаааа [z+4], eax
movаааа eax, [x]
divаааааа [y]
movаааа [z], eax
¦ЁхфюёЄхЁхурхь Tрё юЄ эхтхЁэюую Ёх°хэш :
movаааа edx, 0
movаааа edx, [x+4]
movаааа eax, [x]
divаааааа [y]
movаааа [z], eax
¦Єю Ёх°хэшх ьюцхЄ яЁштхёЄш ъ ю°шсъх л-хыхэшх эр 0¬, хёыш ўрёЄэюх эх яюьх•рхЄё т ЁхушёЄЁ EAX, эряЁшьхЁ, хёыш x>232, р y = 1!
Дополнительные арифметические команды
Для некоторых операций, таких как изменение целого числа на единицу или его знака, предусмотрены специальные команды, заданные в табл. 5.4. Использование этих команд вместо команд общего назначения приводит к более оптимальному коду
Таблица 5.4. Дополнительные арифметические команды
| Назначение | Общий вид | Комментарий | |||
| Увеличение на 1 | inc <Оп> | Оп++ | |||
| Уменьшение на 1 | dec <Оп> | Оп-- | |||
| Инвертирование знака | neg <Оп> | Оп = - Оп |
Команды безусловного перехода
Делятся на команды прямого перехода и команды косвенного перехода, на команды короткие и длинные. Команды прямого перехода требуют, чтобы в команде перехода стояла метка, определенная в данном модуле.
Команды косвенного перехода в качестве операнда принимают адрес с адресом перехода.
В командах короткого перехода для задания смещения, соответствующего метке, используется один байт, в противном случае -четыре.
Общий вид команд безусловного перехода:
JMP [SHORT] операнд
Примеры использования команд перехода:
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
pm1 dd m4, m5, m6
codeseg
begin:
; Короткий переход. Команда занимает 2 байта
jmp short m1
m1:
; Обычный переход. Команда занимает 5 байтов
jmp m2
m2:
m3:
; Использование косвенного перехода.
; Переход на метку m4, m5, m6 или m7 в зависимости от содержимого
; регистра EAX
mov eax, 1; goto m4
jmp [pm1+eax*4]
m4: mov ebx, 4
jmp short m7
m5: mov ebx, 5
jmp short m7
m6: mov ebx, 6
jmp short m7
m7:
call ExitProcess
end begin
Заметим, что в случае ссылки назад, когда метка находится выше, чем команда перехода, слово short
можно не писать, тип перехода определит компилятор.
Коды условий
Арифметические команды формируют коды условий в зависимости от результата выполнения команды:
Перенос (c) – если есть перенос за границы разрядной сетки ячейки;
Четность (p) – количество битов младшего байта результата – четное;
Знак (s) – результат отрицательный;
Нуль (z)– результат равен нулю;
Переполнение (o).
Признак результата записывается в регистр флагов. Регистр флагов – 32 битное число, в котором каждый флаг записывается в фиксированный бит. Для рассмотренных выше флагов используются биты:
C –бит 0
P – бит 2
Z – бит 6
S – бит 7
O – бит 11 .
Содержимое регистра флагов может быть записано в стек (команда pushfd) и прочитано из стека (например, команда pop eax).
Пример. Записать содержимое регистра флагов в регистр EAX:
Pushfd
Pop eax
Команды пересылки не изменяют содержимое регистра флагов!
Специально для изменения битов регистра флагов используются команды:
Команда сравнения : cmp оп1, оп2. Операнды задаются как для арифметических- команд. Фактически выполняется команда вычитания, но результат не записывается вместо первого данного, а только формируется регистр флагов.
Команда test оп1, оп2. Операнды задаются как для арифметических- команд. Фактически выполняется операция поразрядного умножения, но результат не записывается в оп1, а флаги формируются. Команда используется для сравнения с нулем всего числа или отдельных его битов.
Команда bt оп, константа. Команда проверяет содержимое бита, номер которого задан константой, в операнде. Результат записывается в бит переноса регистра флагов.
Команды условного перехода
Общий вид команды:
[Метка] Код метка
Косвенный условный переход не используется. При трансляции команды вместо метки записывается разность между адресом метки и адресом очередной после команды условного перехода команды. Если эта разность помещается в один байт (-128.. 127) команда называется короткой (short). Для команд с ссылкой назад компилятор определяет тип команды сам. Для ссылок назад тип задается программистом, если не задан, принимается обычным. Если программист задал короткий переход, а он невозможен, компилятор выводит сообщение: RELATIVE JUMP OUT OF RANGE
Команды перехода делятся на переходы по флагам, переходы для знаковых и беззнаковых данных.
Таблица 6.1 Команды условного перехода
| Переходы по флагам | Переходы для знаковых данных | Переходы для беззнаковых данных | |||||||||
| Прямой | Обратный | Прямой | Обратный | Прямой | Обратный | ||||||
| Jc | jnc | Jl (<) | Jnl (>=) | Jb(<) | Jnb(>=) | ||||||
| Jp | jnp | Jle (<=) | Jnle(>) | Jbe(<=) | Jnbe(>) | ||||||
| Jz | jnz | Je (==) | Jne(!=) | Je(==) | Jne(!=) | ||||||
| Js | jns | Jg (>) | Jng (<=) | Ja (>) | Jna(<=) | ||||||
| Jo | jno | Jge(>=) | Jnge (<) | jae(>=) | jnae(<) |
Примеры использования команд.
1. Составить программу для вычисления максимального из двух заданных чисел длиной 32 бита (числа со знаком).
Ideal
P586
Model flat
Extrn ExitProcess:proc
Dataseg
X dd 6
Y dd 7
Z dd ?
Codeseg
Begin: mov eax, [X]
Cmp eax, [Y]
Jge short m1
Mov eax, [Y]
M1:
Mov [Z], eax
End begin
2. Составить программу для вычисления минимального для двух 64 беззнаковых чисел.
X dd 0fefefefeh, 0ffffffffh
Y dd 0ffffffffffh, 0ffffffffh
Z dd ?,?
;Пусть сначала заданы младшая, а затем старшая цифра.
Mov eax, [X+4]
Cmp eax, [Y+4]
Ja mx
Jb my
Mov eax, [X]
Cmp eax, [Y]
Jae mx
My:
Mov eax, [Y]
Mov [Z], eax
Mov eax, [Y+4]
Mov [Z+4], eax
Jmp short m1
Mx:
Mov eax, [X]
Mov [X], eax
Mov eax, [X+4]
Mov [X+4], eax
M1:
Пример 3.
Скопировать данные из одной области памяти в другую, если заданы адреса обеих областей и размер
Addr1 dd …
Addr2 dd …
Len dd …
Способ 1
Mov eax, [addr1]
Mov edx, eax
Add edx, [len]
Mov ebx, [addr2]
For1:
Mov cl, [eax]
Mov [ebx], cl
Inc eax
Inc ebx
Cmp eax, edx
Jle for1
…
Ошибка!: команду Jle необходимо заменить командой для сравнения чисел без знака! (Jbe).
Недостаток решения! Не учтено возможное перекрытие областей!
Способ 2
Ab = addr1; h = 1; Be = addr2;
If (addr2 != addr1){
If (addr2 > addr1 && addr2 < addr1+len){
Ab += len – 1; Bb +=len – 1;
H=-1;
}
for (i=0; i
bb+=h;
aa+=h;
}
;Ab = addr1; h = 1; Be = addr2;
mov eax, [addr1]
mov ebx, [addr2]
mov edx, 1
;If (addr2 != addr1){
cmp eax, ebx
je short break
; If (addr2 > addr1 && addr2 < addr1+len){
cmp eax, ebx
jbe m1
mov ecx, eax
add ecx, [len]
cmp ebx, ecx
jae m1
;Ab += len – 1; Bb += len – 1; H=-1;
mov eax, ecx
dec eax
add ebx, [len]
dec ebx
mov edx, -1
;}
;for (i=0; i
for2:
mov cl, [eax]
mov [ebx], cl
add eax, edx
add ebx, edx
sub esi, 1
jnz for2
Специальные команды
Команда loop:
Loop <метка>
Выполняемые действия:
Ecx--; if (ecx) goto <метка>
Структура циклического участка программы:
; Подготовка цикла (формирование начальных значений параметров, счетчика)
<метка>:
; Циклический участок программы
Loop <метка>
Пример 1. Составить программу для вычисления суммы 1+2+3 +… + 100[9]
For (i=1, s=0; i<=100; i++) s+=i;
Mov eax, 0; s=0
Mov ecx, 100
For1:
Add eax, ecx
Loop for1
Команды Loopz, Loopnz дополнительно с условием ecx = 0 проверяют флаг нуля. Если флаг имеет заданное значение – то остаемся в цикле.
Команда jecxz – команда условного перехода при ecx, равном нулю – защита от “зацикливания”
Заметим, что использование команды Loop менее эффективно, чем простых команд: dec, jnz
Организация вложенных циклов
Пусть необходимо реализовать участок программы:
For (i=0; i
Можно для параметров цикла использовать разные регистры – но регистров мало. В этом случае, если для параметров достаточно 16 битных значений можно использовать один регистр для двух параметров.
Пример 1. Пусть необходимо реализовать участок программы
For (i=0; i<5; i++)
For (j=0; j<6; j++)
…
; For (i=0; i<5; i++)
mov ecx, 5
shl ecx, 16; Счетчик в старшую часть регистра
for1:
; For (j=0; j<6; j++)
mov cx, 6
for2:
…
dec cx
jnz for2
sub ecx, 10000h
jnz for1
Пример 3. Составить программу для вычисления произведений всех элементов вида a[0] * b[0], a[0] * b[1], … a[0] * b[m-1] ,…a[n-1] * b[m-1]:
P=1;
For (i=0; i
; P=1;
mov eax, 1
;For (i=0; i
shl ecx, 16
mov ebx, offset a
; For (j=0; j
mov cx, [m]
mov esi, offset b
; P=p*a[i]*b[j];
forj:
mul [dword ptr ebx]
mul [dword ptr esi]
;}
add esi, 4
dec cx
jnz forj
add ebx, 4
dec ecx, 4
jnz fori
Монотонное изменение индекса
Индекс изменяется монотонно, если он увеличивается или уменьшается с постоянным шагом.
Для адресации элементов массива используются способы адресации [см. 3.4.3]:
<Имя массива + смещение> - для адресации заданного элемента массива - относительная адресация. Смещение - константное выражение;
[Регистр] - регистр содержит адрес текущего элемента массива - базисная адресация. Пусть индекс массива изменяется в пределах [p:q]. В этом случае адрес i-ого элемента массива равен адресу начала массива + (i-p)* l, где l - размер одного элемента массива. Заметим, что формула упрощается при p равном нулю. В дальнейшем будем предполагать, что минимальное значение индекса всегда равно 0.
[Регистр + смещение] (базисная адресация)- Смещение - константное выражение - используется, если в программе одновременно используются i-ый и i+...-ый элемент массива;
[Имя массива + регистр * масштаб] (индексная адресация)- регистр задает значение индекса в массиве - используется при работе с массивами, размер элементов которых равен масштабному множителю.
Пример 1. Составить программу для вычисления

Вариант 1. Использование базисной адресации
For (s=i=0; i
mov ecx, [n]; i
mov ebx, offset x
; s+=x[i];
fori:
add eax, [ebx]
; i++
add ebx, 4
; i
mov [s], eax
Вариант 2. Индексная адресация
;For (s=i=0; i
sub ebx, ebx; i=0
mov ecx, [n]
for1:
add eax, [x+ebx*4]
inc ebx
loop for1
mov [s], eax
Вариант 3. Так как суммирование можно выполнять, начиная с последнего элемента, регистр ecx можно использовать в качестве индексного регистра
sub eax, eax
mov ecx, [n]
for1:
add eax, [x+ ecx*4-4]
loop for1
mov [s], eax
Сравнение программ показывает, что 3 вариант наиболее выгодный с точки зрения числа команд.
¦ЁшьхЁ 2.
TюёЄртшЄ№ яЁюуЁрььє фы тvўшёыхэш ёєьь:
y0 = x0 + xn-1;
y1 = x1 + xn-2;
...
yn/2 = xn/2 + xn/2+1;
for (i=0, j=n-1; i
}
movаааа ecx, [n]
movаааа edx, ecx
shrаааааа ecx, 1
jecxzааа m1
movаааа eax, 0
for1:
movаааа ebx, [x+eax*4]; x[i]
addааааа ebx, [x+ecx*4-4]
movаааа [y+eax*4], ebx
incаааааа eax
decаааааа edx
loopаааа for1
m1:
а ¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы эрїюцфхэш ьръёшьры№эюую ўшёыр ш хую эюьхЁр т ьрёёштх ўшёхы фышэющ 32 сшЄр.
For (max = x[nom = 0], i=1; i
ааааа max = x[i]; nom = i;
аа }
movаааа eax, 0; nom
movаааа ebx, [x]; max
movаааа ecx, [n]
decаааааа ecx
jecxzааа m1
movаааа edx, 1; i
for3:
movаааа esi, [x+edx*4]
cmpаааа esi, ebx
jleааааааа short next
movаааа ebx, esi
movаааа eax, edx
next:
incаааааа edx
loopаааа for3
movаааа [max], ebx
movаааа [nom], eax
-ы Єюую ўЄюсv ЁхушёЄЁ ECX шёяюы№чютрЄ№ фы рфЁхёрЎшш фрээvї шчьхэшь яЁюуЁрььє Єръшь юсЁрчюь, ўЄюсv яюшёъ эрўшэрыё ё яюёыхфэхую ўшёыр
for (i=n-2, max = x[nom = n-1]; i>=0; i-- ){
аа if (x[i]>max) {max= x[i]; nom = i;}
}
movаааа ecx, [n]
decаааааа ecx
jecxzааа m1
movаааа eax, ecx
movаааа ebx, [x+ecx*4]
for4:
movаааа esi, [x+ecx * 4 - 4]
cmpаааа esi, ebx
jleааааааа short next1
movаааа eax, ecx
movаааа ebx, esi
loopаааа for4
movаааа [max], ebx
movаааа [nom], eax
¦рёёьюЄЁшь яЁшьхЁ ЁрсюЄv ёю ёЄЁюърьш.
¦ЁшьхЁ 5. TюёЄртшЄ№ яЁюуЁрььє фы юс·хфшэхэш фтєї ёЄЁюъ ё эєыхтvь чртхЁ°шЄхыхь т юфэє ёЄЁюъє.
For (i=0; str1[i]; i++) str3[i] = str1[i];
for (j=0; str3[i+j=str2[j]; j++);
; For (i=0; str1[i]; i++) str3[i] = str1[i];
movаааа eax, 0; i=0
for6:
movаааа bl, [str1+eax]
testаааааа bl, bl
jeаааааааа next5
movаааа [str3+eax], bl
incаааааа eax
jmpааааа for6
next5:
movаааа ecx, 0
for7:
movаааа bl, [str2+ecx]
movаааа [str3+ecx+eax], bl
testаааааа bl, bl
jeаааааааа short mmm
incаааааа ecx
jmp for7
mmm:
Немонотонное изменение параметра цикла
Пусть индекс изменяется по произвольному закону. В этом случае сначала вычисляется значение индекса, а затем выполняются действия для элемента.
Пример 1. Составить программу для нахождения заданного элемента в массиве целых упорядоченных чисел.
nom=-1;
i=0; j=n-1;
while (i<=j){
k=(i+i)/2;
if (x[k]==r){
nom = k;
break;
}
if (x[k]
;nom=-1;
mov eax, -1
;i=0; j=n-1;
mov ebx, 0
mov ecx, [n]
dec ecx
;while (i<=j){
cmp ebx, ecx
jg short m1
; k=(i+i)/2;
mm9:
mov edx, ebx
add edx, ecx
shr edx, 1
; if (x[k]==r){
mov esi, [x+edx*4]
cmp esi, [r]
jne mm6
;nom = k;
mov [nom], edx
;break;
jmp short mm7
;}
;if (x[k]
cmp esi, [r]
jge short mm8
mov ebx, edx
inc ebx
jmp short mm9
mm8:
mov ecx, edx
dec ecx
jmp short mm9
;}
Пример 2. В заданной строке символов переставить символы в порядке, заданном числовым массивом
for (i=0; str1[i]; i++){
ch = str1[i];
n = nom[i];
str2[n] = ch;
}
str2[i] = 0;
str1 db ‘abcdef’, 0
nom dd 5, 1, 0, 2, 4, 3
str2 db (nom - str1) dup (?)
...
;for (i=0; str1[i]; i++){
mov eax, 0
;ch = str1[i];
m2:
mov bl, [str1+eax]
test bl, bl
je short m1
;n = nom[i];
mov ecx, [nom+eax]
;n = nom[i];
mov [str2+ecx], bl
inc eax
jmp m2
m1:
Использование команд для работы с блоками
Команды позволяют обрабатывать массив чисел длиной 1, 2, 4 байтов, начиная с начала или конца массива. При этом автоматически изменяется адрес элемента массива на длину элемента массива. Для массива можно выполнить следующие операции:
копирование массива в другой массив:
сравнение двух массивов;
заполнение массива заданным значением или последовательным чтением элементов массива;
поиск заданного элемента массива.
Для определения направления поиска устанавливается флаг d регистра флагов (бит № 10).
Адрес исходного массива задается в регистре ESI, а результирующего - в регистре EDI.
Количество элементов массива задается в регистре ECX.
Команды :
MOVS{B|W|D} - do{*(edi++) = *(esi++) while (ecx--);}
LODS{B|W|D} {AL|AX|EAX} = *(esi++)
STOS{B|W|D} *(edi++) = {AL|AX|EAX}
CMPS{B|W|D} сравнение *(esi++) и *(edi++)
SCAS{B|W|D} do{ if {AL|AX|EAX} = (!=) ) *(edi++) break; while (ecx--);
В таблице 7.1 представлена сводная таблица команд для работы с блоками
Таблица 7.1. Сводная таблица команд для работы с блоками
| Код | Выполняемые действия | Подготовка команды | Используемый префикс | ||||
| MOVS | dest = src | cld (std); esi = &src[10] edi = & dest ecx = number | REP | ||||
| LODS | {al, ax, eax} = *esi++ | cld (std); esi = &src | Префикс не используется | ||||
| STOS | *edi++ = {al, ax, eax} | cld (std); edi = &dest ecx = number | REP | ||||
| CMPS | *src ++ <>*dest++ | cld (std); esi = &src7 edi = & dest ecx = number | REPE, REPNE | ||||
| SCAS | {al, ax, eax} <> *dest++ | EAX - что ищем, edi = & dest (где ищем) ecx = number | REPE, REPNE |
Примеры использования команд
Скопировать область памяти заданной длины в другую область памяти, если адрес начала первой области f1, ее размер len байт, адрес начала второй области f2. Пусть len кратно 4.
В этой программе сначала определяется направление копирования. Если области памяти для исходного и результирующего массивов пересекаются, при этом адрес начала результирующего массива больше, чем адрес исходного, необходимо копировать, начиная с конца массива, в противном случае – с начала.
Е
Movаааа esi, [f1]
Movаааа edi, [f2]
Movаааа ecx, [len]
Shrаааааа ecx, 2
Jecxzааа m1
cld
cmpаааа esi, edi
jaаааааааа short m2
jeаааааааа break
movаааа esi, eax
decаааааа esi
addааааа edi, [len]
decаааааа edi
std
m2:
repаааааа movsd
break:
cld
¦ЁшьхЁ 2. T чрфрээющ ёЄЁюъх ьрыхэ№ъшх ырЄшэёъшх сєътv чрьхэшЄ№ сюы№°шьш.
Str1ааааа dbааааааа СThis is stringТ,0
Е
cld
movаааа esi, offset str1
movаааа edi, offset str1
for1:
lodsb
testаааааа al, al
jeаааааааа short break
cmpаааа al, СaТ
jbаааааааа notl
cmpаааа al, СzТ
jaаааааааа notl
addааааа al, СAТ Ц СaТ
notl:
stosb
jmp for1
break:
cld
¦ЁшьхЁ 3. TюёЄртшЄ№ яЁюуЁрььє фы ёЁртэхэш фтєї фышээvї ўшёхы юфшэръютющ фышэv. ¦ЁюуЁрььр фюыцэр ЇюЁьшЁютрЄ№ Ёхчєы№ЄрЄ >0, хёыш яхЁтюх ўшёыю сюы№°х тЄюЁюую, Ёртэю 0, хёыш ўшёыр Ёртэv ш <0, хёыш яхЁтюх ўшёыю ьхэ№°х тЄюЁюую. ¦єёЄ№ ўшёыю ёюёЄюшЄ шч n 32-сшЄэvї лЎшЇЁ¬ ш чряшёvтрхЄё , эрўшэр ё ьырф°шї ЎшЇЁ.
Xааааааааа ddааааааа Е, Е., Е.
Yааааааааа ddааааааа Е, Е, Е
Nаааааааа ddааааааа (Y Ц X)/4
Resааааа ddааааааа ?
Е
subаааааа eax, eax
movаааа ecx, [N]
leaаааааа esi , [X+ecx*4-4]
leaаааааа edi , [Y+ecx*4-4]
std
repeаааа cmpsd
jecxzааа m1
jbаааааааа letter
movаааа eax, 1
jmpааааа short m1
letter: movаааааа eax, -1
m1:
cld
ааааааааа
¦ЁшьхЁ 4.
T чрфрээюь ьрёёштх ўшёхы тёх эєыхтvх чэрўхэш чрьхэшЄ№ Ц1
While (){
аа If (хёЄ№ эєыхтюх чэрўхэшх)
аа ааа¦рьхэшЄ№;
аа Else
ааааа Break;
}
movаааа ecx, [n]
cld
subаааааа eax, eax
movаааа edi, offset x
for1:
repneаа scasd
jecxzааа break
movаааа [dword ptr edi-4], -1
jmpааааа for1
Задание двухмерного массива
Пусть необходимо задать матрицу, определенную в С:
int matr[][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
matr dd 1,2,3,4
dd 5,6,7,8
dd 9,10,11,12
Задание адреса элемента
Для адресации данных массива можно использовать 2 регистра индексный и базисный [3.4.3]. Для каждого индекса определяется шаг изменения адреса.
Например, для массива:
int x[M][N] шаг изменения адреса по второму индексу Hj равен sizeof (int) = 4, а для первого индекса Hi равен Hj * N. Если длина элемента совпадает со значением масштабного множителя (стандартная длина элемента -1, 2, 4, 8), то в индексный регистр записывается значение индекса, если не совпадает то - значение смещения.
Для задания адреса элемента X[i][j] со стандартной длиной элемента используется запись вида [X+REG1 + REG2 * LEN], где REG1- общий регистр, содержащий смещение для i - ой строки, а REG2-
общий регистр, содержащий значение индекса j. Для элемента массива нестандартной длины адрес задается в виде [X+REG1 + REG2], где REG1- общий регистр, содержащий смещение для i - ой строки, а REG2- общий регистр, содержащий смещение для j - ого элемента строки.
Пример. Оттранслировать операторы:
int matr[][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
long double numbers [][4]= {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
// Формирование i, j
matr[i][j]=0; numbers[i][j]=0;
matr dd 1,2,3,4
dd 5,6,7,8
dd 9,10,11,12
numbers
dt 1,2,3,4
dt 5,6,7,8
dt 9,10,11,12
HIMATR dd 16
HINumbers dd 40
...
mov eax, HIMATR
mul [i]
mov ebx, [j]
mov [matr+eax+ebx*4], 0
mov eax, HINumbers
mul [i]
mov ebx, eax; База
mov eax, 10
mul [j]
mov [dword ptr numbers + eax + ebx], 0
mov [dword ptr numbers + eax + ebx + 4], 0
mov [word ptr numbers + eax + ebx + 8], 0
Монотонное изменение индекса
Структура программы с монотонным изменением индексов:
Вычисление Hj , Hi
Цикл для вычисления.
Пример. Составить программу для вычисления сумм столбцов матрицы:
for (j=0; j
for (i=0; i
}
Для оптимизации программы запишем:
for (j=0; j
for (i=0; i
s[j]=r;
}
; Вычисление Hi =4*N, Hj=4
mov eax, [N]
shl eax, 2; Hi
mov ebx, 4; Hj
;for (j=0; j
shl ecx, 16
mov edx, 0; j
forj:
; r=0;
mov esi, 0
; for (i=0; i
mov edi, 0; i
; r+=matr[i][j]
fori:
add esi, [matr + edi + edx*4]
; i++
add edi, eax
dec cx
jnz fori
; s[j]=r;
mov [s+edx*4], esi
;j++
inc edx
sub ecx, 10000h
jnz forj
}
...
Недостатки метода:
для каждого индекса свой регистр;
не может быть обобщен на массивы большей размерности.
Метод связанных индексов
Адрес элемента в начале строки используется для формирования адреса очередного элемента для обоих индексов, т.е. фактически требуется 2 экземпляра этого адреса. Т.к. фактически используется одно и тоже значение для работы с каждым индексом метод называется методом связанных индексов. Для получения 2-х экземпляров используется стек. Формируется необходимое значение, один экземпляр которого записывается в стек, второй остается в регистре для работы со вторым индексом. При необходимости работы с первым индексом извлекается значение из стека.
Структура программы для метода связных индексов:
for (i=0; i<.. i++){ mov ecx, ...
mov ebx, Смещение ПОЭ[11]
...
for (j=0; j<... ; j++){ fori:
... push ecx
push ebx
mov ecx,...
forj:
[Имя массива + ebx]
} add ebx, [Hj]
loop forj
} pop ebx
add ebx, Hi
pop ecx
loop fori
Пример 1. Составить программу для нахождения сумм строк матрицы с нечетными номерами
for (k=i=0; i
for (j=0; j
s[k]=r;
}
mov esi, [N]
shl esi, 3; Шаг по i
;for (k=0. i=1; i
mov ecx, [M]
shr ecx, 1
mov ebx, [N]
shl ebx, 2; i
fori:
;r=0;
subаааааа edx, edx
;аа for (j=0; j
movаааа ecx, [N]
pushаааа ebx
forj:
;ааааа r+=matr[i][j]
addааааа edx, [matr+ebx]
;j++
addааааа ebx, 4
loopаааа forj
;аа s[k]=r;
movаааа [s+eax*4], edx
;i++
incаааааа eax
popааааа ebx
addааааа ebx, esi
popааааа ecx
loop fori
¦ЁшьхЁ 2. TюёЄртшЄ№ яЁюуЁрььє фы яхЁхьэюцхэш ьрЄЁшЎ
ideal
p586
model flat
extrn ExitProcess:proc
dataseg
matrаааааа ddааааа 1,1,1,1,1
аааааааааа ddааааа 2,2,2,2,2
аааааааааа ddааааа 3,3,3,3,3
аааааааааа ddааааа 4,4,4,4,4
Mааааааааа ddааааа 4
Nааааааааа ddааааа 5
Sааааааааа ddааааа 5 dup (?)
codeseg
begin:
movааа esi, [N]
shlааа esi, 3; ? г по i
;for (k=0. i=1; i
movа ecx, [M]
shrа ecx, 1
movа ebx, [N]
shlа ebx, 2; i
fori:
;r=0;
subаа edx, edx
;аа for (j=0; j
movаааа ecx, [N]
pushааа ebx
forj:
;ааааа r+=matr[i][j]
addааа edx, [matr+ebx]
;j++
addа ebx, 4
loopааааа forj
;аа s[k]=r;
movаааааааа [S+eax*4], edx
;i++
incа eax
popа ebx
addа ebx, esi
popа ecx
loop fori
callааа ExitProcess
endаааа begin
¦ръ тшфэю шч яЁюуЁрььv фы ърцфюую шч ьрёёштют ЄЁхсєхЄё Єюы№ъю юфшэ ЁхушёЄЁ.
Использование 2-х мерного массива при немонотонном изменении индекса
Пусть задана разреженная матрица, содержащая очень много нулей.
Для упрощения задания определяются только ненулевые элементы матрицы в виде:
x, y, value,
где x, y - координаты ненулевого значения. Составить программу для заполнения матрицы заданными значениями.
Для решения этой задачи потребуется задать адрес элемента массива matr[x][y], который вычисляется по формуле:
& matr[x][y] = & matr[0][0] + x * Hx + y * Hy
; Задание матриц
PackMatr dd 1, 3, 10
dd 2, 7, 20
dd 4, 4, 30
count dd 3
UnpackMatr dd 5 dup (5 dup 0)
hi dd 5*4
; Заполнение матрицы UnpackMatr:
mov ecx, [count]
mov ebx, offset PackMatr
for1:
mov eax, [ebx] ;x
mul [hi] ; x * hi
mov edx, [ebx+4] ;y
mov esi, [ebx+8] ;value
mov [UnpackMatr+eax+edx*4], esi
add ebx, 12
loop for1
Использование метода связных индексов для многомерных массивов
Пример. Составить программу для реализации фрагмента программы:
int a[3][5][4];
for (k=1, l=0; k<4; k+=2, l++){
r=0;
for (i=0; i<3; i++)
for(j=0; j<5; j++)
r+=a[i][j][k];
s[l]=r;
}
...
dataseg
a dd 1,1,1,1
dd 2,2,2,2
dd 3,3,3,3
dd 4,4,4,4
dd 5,5,5,5
dd 1,1,1,1
dd 2,2,2,2
dd 3,3,3,3
dd 4,4,4,4
dd 5,5,5,5
dd 1,1,1,1
dd 2,2,2,2
dd 3,3,3,3
dd 4,4,4,4
dd 5,5,5,5
s dd 2 dup(?)
hk dd 4
hj dd 16
hi dd 16*5
codeseg
begin:
;for (k=1, l=0; k<4; k+=2, l++){
mov ecx, 4
shr ecx, 1
mov ebx,offset a
add ebx, 4
mov esi, offset s
fork:
;r=0;
sub edi, edi
;for (i=0; i<3; i++)
push ebx
push ecx
mov ecx, 3
;for(j=0; j<5; j++)
fori:
push ebx
push ecx
mov ecx, 3
forj:
;r+=a[i][j][k];
add edi, [ebx]
;j++
add ebx, [hj]
loop forj
;i++
pop ecx
pop ebx
add ebx, [hi]
loop fori
;s[l]=r;
mov [esi], edi
;l++
add esi, 4
pop ecx
pop ebx
add ebx, 8
loop fork
;}
Использование метода приведенных индексов для многомерных массивов
Пусть определение массива имеет вид:
Тип Имя [p1..q1, p2..q2, ...pn..qn], где pi..qi- минимальное и максимальное значения i-ого индекса
Адрес элемента массива с индексами i1, i2, ...in определяется формулой[12]:
& Имя[i1, i2, ...in] = Адресу начала массива +


Формулы для вычисления адреса:
Hn = sizeof (Тип);
Hn-1 = Hn * (qn
- pn +1);
...
H1 = H2 * (q2
- p2 + 1).
Формулы вычисления адреса показывают, что наиболее простой вид формула имеет при начальном значении индексов, равном 0.
Пример1. Реализовать фрагмент программы:
int x[3][4][5][6];
...
x[i][j][k][l]=i+j+k+l;
x dd (3*4*5*6) dup (?)
i dd 1
j dd 2
k dd 3
l dd 4
hl equ 4
hk equ 6*hl
hj equ 5*hk
hi equ 4*hj
...
mov eax, [i]
imul eax, hi
mov ebx, [j]
imul ebx, hj
add eax, ebx
mov ebx, [k]
imul ebx, [hk]
add eax, ebx
mov ebx, [l]
mov ecx, [i]
add ecx, [j]
add ecx, [k]
add ecx, [l]
mov [x+eax+ebx*4], ecx
…
Последний метод используют для доступа к элементам массива компиляторы с языков высокого уровня, поэтому использование переменных с индексами, особенно для многомерных массивов, требует большого времени для вычисления адреса элемента массива.
Многомерные массивы
Для многомерных массивов используются:
метод связных индексов при монотонном изменении индекса
метод приведенных индексов, когда вычисляется адрес элемента массива в соответствующем одномерном массиве.
Задание типа структуры
Общий вид задания типа:
Struc Имя
поле1
поле2
...
ends [Имя]
Полями структуры могут быть директивы определения данных, в том числе и другие структуры, т.е. допускается вложение структур.
Пример. Определить тип для задания структуры «Возраст», содержащей день рождения в виде:
Год(2 байта) Месяц (1 байт) День (1 байт)
STRUC AGE
day db ?
month db ?
year dw ?
ends
Выделение памяти под структуру
Для выделения памяти используется запись вида:
Имя поля Тип структуры ? или значение полей структуры.
Пример:
age1 AGE ?
age2 AGE ?
Как и для стандартных типов данных, можно задавать коэффициенты повторений.
AgeList AGE 50 dup (?)
Имена полей глобальны, т.е. нельзя использовать одно и то же имя для разных структур или это имя для простых переменных.
Инициализация полей структуры
Используется 3 способа инициализации.
Способ 1. Значения полей задаются в задании типа структуры, например:
STRUC AGE
day db 1
month db 1
year dw 1980
ends
Эти значения переносятся в поля структуры, если для них не используются другие способы задания.
Способ 2. Позиционное задание полей. Значения полей задаются в порядке их следования в определении данного через запятую в угловых скобках, например
age1 AGE < 1,2, 1979>
age2 AGE < 25, 10, 1981,>
Если значение какого-то поля переопределять не нужно, оно не записывается, но запятая ставится, например:
age3 AGE <25, 10,>
В этом случае год рождения 1980, как определено в задании типа.
Если не определяется значение последних полей, запятые можно не ставить, например:
age4 AGE <25 >
Здесь месяц и год определяются из задания типа.
Если значения полей не надо переопределять, записываются просто угловые скобки, например
age5 AGE < >
Способ 3. Непозиционное задание полей. Значения полей задаются в произвольном порядке в фигурных скобках. Для каждого значения используется формат:
Имя поля = Значение.
Определения разделяются запятыми. Пример.
age5 AGE {day = 3, year = 1990, month = 2}
Особенности инициализации строк и массивов
Пусть полем структуры является строка длиной 10 символов. Чтобы можно было задавать начальные значения этому полю, необходимо его представление в виде:
pole1 db ‘ ’
В кавычках задано требуемое количество пробелов. Если строка задана в виде 10 dup ( ), компилятор ее воспринимает как данное типа 1 байт и допускает инициализацию только одного байта. При задании числовых массивов или используются разные имена для отдельных элементов, или инициализация не допустима.
Обращение к полям структуры
Для обращения к полю по имени структуры используется запись вида:
Имя структуры. Имя поля.
Если адрес структуры задан в регистре, то поле структуры задается в виде [(Имя структуры reg). Имя поля].
Пример. Задан массив с возрастами. Определить возраст самого старшего человека списка
ideal
p386
model flat
extrn ExitProcess:proc
dataseg
STRUC AGE
day db 1
month db 1
year dw 1980
ends
list AGE < 3,10>, <1,1,1993>
AGE < 1,1, 1989>, <>
count dd 4
old AGE ?
codeseg
begin:
mov eax, offset list
mov bx, [(AGE eax).year]
mov dl, [(AGE eax).month]
mov dh, [(AGE eax).day]
mov ecx, [count]
dec ecx
add eax, 4
for1:
cmp bx, [(AGE eax).year]
jl short next
jg write
cmp dl, [(AGE eax).month]
jl short next
jg short write
cmp dh, [(AGE eax).day]
jle short next
write:
mov bx, [(AGE eax).year]
mov dl, [(AGE eax).month]
mov dh, [(AGE eax).day]
next:
add eax, 4
loop for1
mov [old.year], bx
mov [old.month], dl
mov [old.day], dh
call ExitProcess
end begin
Вариант 2.
Использовать для сравнения целиком всю запись.
ideal
p386
model flat
extrn ExitProcess:proc
dataseg
STRUC AGE
day db 1
month db 1
year dw 1980
ends
list AGE < 3,10>, <1,1,1993>, < 1,1, 1989>, <>
count dd 4
old AGE ?
codeseg
begin:
mov eax, offset list
mov ebx, [ eax]
mov ecx, [count]
dec ecx
for1:
add eax, 4
cmp ebx, [ eax]
jle short next
write:
mov ebx, [ eax]
next:
loop for1
mov [dword old], ebx
call ExitProcess
end begin
Пример 2. Составить программу для упорядочивания списка студентов в порядке убывания среднего балла
Способ 1. В запись включить фамилию и оценки - недостаток - размер фамилии может содержать разное число символов - надо выделять память по максимуму.
Способ 2. Записать список фамилий. Адрес каждой фамилии -в запись. Достоинства:
памяти, сколько требуется;
при сортировке перемещается не строка, а ее адрес - уменьшается требуемое время.
Lёяюы№чєхь фы єяюЁ фюўштрэш ьхЄюф яюфёўхЄр .
TєЄ№ ьхЄюфр: -ы ърцфюую ўшёыр юяЁхфхы хь ъюышўхёЄтю ўшёхы, яЁхф°хёЄтє¦•шї фрээюьє ўшёыє. ¦рЄхь ўшёыр ёЄрт Єё эр ётюш ьхёЄр т фЁєуюь ьрёёштх.
; +яЁхфхыхэшх ъюышўхёЄтр яЁхф°хёЄтє¦•шї ўшёхы
аfor (i=0; i
; ¦ряшё№ ўшёхы эр ётюш ьхёЄр
for (i=0; i
ideal
p386
modelаа flat
extrn ExitProcess:proc
dataseg
fio1ааа dbааааа 'aaaaaaaa', 0
fio2ааа dbааааа 'bbbbbb', 0
fio3ааа dbааааа 'cccccccccccccccc', 0
fio4ааа dbааааа 'ddddddd', 0
fio5ааа dbааааа 'eeeeeeeeeeeeeeeeeeee', 0
struc аа data1
pfioаааааа ddаа ?
oc1ааааааа dbаа ?
oc2ааааааа dbаа ?
oc3ааааааа dbаа ?
oc4ааааааа dbаа ?
ends
gr1ааааааа data1а < offset fio1, 2, 3, 4, 5>, < offset fio2, 3,4,5,3>
аааааааааа data1а < offset fio3, 5, 5, 5, 5>, < offset fio4, 5,4,5,5>
аааааааааа data1а < offset fio5, 5, 4, 4, 4>
countааааа ddааа 5
rааааааааа data1 5 dup (?)
sааааааааа ddаа 5 dup(0)
codeseg
begin:
;for (i=1; i
decа ecx
movаааа eax, 1
movа esi, offset gr1
addааааа esi, 8
fori:
movа bl,0
addа bl, [(data1 esi).oc1]
addа bl, [(data1 esi).oc2]
addа bl, [(data1 esi).oc3]
addа bl, [(data1 esi).oc4]
;аа for (j=0; j movаааа edx, 0
movаааа edi,offset gr1
forj:
cmpаааа edx, eax
jgeаааа breakj
movаааа bh, 0
addаааа bh, [(data1 edi).oc1]
addаааа bh, [(data1 edi).oc2]
addаааа bh, [(data1 edi).oc1]
addаааа bh, [(data1 edi).oc1]
;ааааа if (x[j]>x[i]) s[j]+=1; else s[i]+=1;
cmpааа bh, bl
jgeааа short mge
incааа [s+edx*4]
jmpааа short nextj
mge:
incааа [s+eax*4]
nextj:
incааа edx
addааааа edi, 8
jmpаа forj
;i++
breakj:
incа eax
addааааа esi, 8
loopаааа fori
;for (i=0; i
movа eax, 0
for2:
;аа y[s[i]]=x[i];
leaаааааа esi, [ gr1 + eax* 8]
movа ebx, [s+eax*4]
leaаааааа edi, [r+ebx*8]
movа edx, [(data1 esi).pfio]
movа [(data1 edi).pfio], edx
movа edx, [dwordа (data1 esi).oc1]
movа [dword (data1 edi).oc1], edx
;i++
incа eax
loopаа for2
call ExitProcess
end begin
Структуры
Используются для задания полей разной длины, входящих в одну запись.
Особенности инициализации полей объединения
В отличие от структур можно инициализировать только элемент первого поля. Для инициализации можно использовать такие же способы, как для структуры.
Пример. В предыдущей задаче поле оценок использовалось как 4 байта и одно 4-х байтовое слово. Задать это с помощью объединения.
STRUC STRUC1
oc1 db ?
oc2 db ?
oc3 db ?
oc4 db ?
ends
UNION UNION1
st1 STRUC1 <5,5,5,5>
st2 dd ?
ends
STRUC DATA1
pfio dd ?
oc UNION1 ?
ends
Для доступа к полям в этом случае используется запись вида:
data1.oc.st1.oc1
Объединения
Используются, если необходимо одно и тоже поле памяти использовать как данные разного типа. Отдельные поля объединения записываются в памяти, начиная с одного и того же адреса - это отличие от структуры.
Общий вид объединения
UNION Имя
поле1
поле2
...
ends [Имя]
Требования и способы задания полей как для структур.
Перечисления
Общий вид:
Имя типа ENUM {Имя поля [= значение], Имя поля [= значение],... }
Перечисление используется для задания группы констант, чтобы не повторять директивы EQU или =. Имя поля является именем константы. Значение первого поля, если оно не задано, принимается равным 0, значение любого другого поля, если оно не задано, равно значению предыдущего, увеличенному на 1. Значения разных полей в списке могут повторяться.
Пример.
Запись COLORS ENUM {BLUE, GREEN, RED} эквивалентна определению трех констант:
BLUE EQU 0
GREEN EQU 1
RED EQU 2
Поля объединений можно использовать в любом месте, где допускается использование константы.
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Зубков С.М. Assembler. Для Dos, Windows и Unix. - М.: ДМК. 1999. 640 с.
2. Юров В.И. “Ассемблер: Учебный курс”. Питер, 1998
3. Рихтер Джефри. «Профессиональное программирование для Windows 2000, NT», Питер, 2000
4. Качко Е.Г. Программирование на ассемблере. Харьков, ХТУРЭ, 1997
5. Методические указания к практическим занятиям и самостоятельной работе по курсу «Системное программирование», // Качко Е.Г., Белецкий Е.В., Мельникова Р.В. Харьков, ХТУРЭ, 1998.
[1]
Настоятельно рекомендуем использовать этот принцип для программ собственной разработки.
[2]
Это плохой стиль написания программы, но он здесь использован для демонстрации команды условного перехода
[3]
Точнее, автору не известны эти обозначения
[4]
Уточнение адреса для страничного режима в данном курсе не рассматривается
[5]
Не огорчайтесь, если материал этого раздела для Вас не совсем понятен. Постепенно вы научитесь правильно использовать все способы адресации
[6]
Вычисление остатка от деления
[7]
Обычные знаки типа <, > не используются, т.к. зарезирвированы для применения в макросах
[8]
Предполагается, что цифры числа задаются в памяти, начиная с младших цифр, т.е. чем младше цифра, тем меньше ее адрес
[9]
Для решения этой задачи можно использовать формулу суммы арифметической прогрессии. Цикл используется для демонстрации методики применения циклов
[10]
Задаются адреса первых обрабатываемых элементов
[11]
ПОЭ – первый обрабатываемый элемент массива
[12]
Формула может быть доказана методом математической индукции.
[13]
Вычислите, в каком году возникнет «проблема 2000 года!»
[14]
Вызов может быть прямой и косвенный. При прямом вызове задается имя процедуры, при косвенном - адрес с адресом процедуры.
[15]
Для С++ Builder метка может быть записана внутри ассемблерного кода
[16]
Оптимизационные способности современных компиляторов настолько высоки, что не имеет смысла применять регистровые переменные
[17]
Здесь и далее в таблице многоточию соответствуют первые буквы типов параметров в порядке их следования в заголовке
[18]
Функции заданы для однобайтной кодировки символов.
[19]
Все функции WINDOWS API, которые возвращают значения типа BOOL, устанавливают его равным TRUE при благополучном завершении функции и FALSE в случае ошибки
[20]
Это ограничение снято для PENTIUM III.
[21]
Если операционная система не восстанавливает этот бит при переключении задач, рекомендуется выполнять переключение сразу после исполнения участка кода с MMX.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования

