![]() |
Структуры управления в языке ассемблера |
![]() |
Структуры управления и регистр FLAGS
Структурами управления (control structures) называются блоки программ, анализирующие значения переменных, и выбирающие на основании их направление процесса дальнейшего выполнения программы. В языках высокого уровня (относительно ассемблера) такими блоками являются операторы IF, IF-ELSE, WHILE, DO-WHILE, FOR. В языке ассемблера, однако, структуры управления выражаются более низкоуровневыми инструкциями, каждая из которых, в той или иной мере, использует регистр флагов FLAGS. Как и другие регистры общего назначения на 64-битной архитектуре, регистр RFLAGS имеет размер 64 бита и содержит в себе 32-битный регистр EFLAGS, который, в свою очередь, содержит упомянутый 16-битный регистр FLAGS.
Особенностью флаговых регистров является невозможность напрямую прочитать значение какого-либо флага в регистр общего назначения. Значения некоторых флагов, однако, можно менять с помощью специальных инструкций. Подробное описание регистра FLAGS приведём в виде таблицы:
№ бита |
Название |
Тип флага |
Описание |
0 |
CF (Carry) |
Состояние |
Флаг переноса |
1 |
1 |
Резерв |
Зарезервированный флаг |
2 |
PF (Parity) |
Состояние |
Флаг четности |
3 |
0 |
Резерв |
Зарезервированный флаг |
4 |
AF (Auxiliary Carry) |
Состояние |
Вспомогательный флаг переноса |
5 |
0 |
Резерв |
Зарезервированный флаг |
6 |
ZF (Zero) |
Состояние |
Флаг нуля |
7 |
SF (Sign) |
Состояние |
Флаг знака |
8 |
TF (Trap) |
Системный |
Флаг трассировки |
9 |
IF (Interrupt On) |
Системный |
Флаг разрешения прерываний |
10 |
DF (Direction) |
Управление |
Флаг направления |
11 |
OF (Overflow) |
Состояние |
Флаг переполнения |
12 |
IOPL (I/O Privilege Level) |
Системный |
Флаг уровня приоритета ввода/вывода |
13 |
NT (Nested Task) |
Системный |
Флаг вложенности задач |
14 |
0 |
Резерв |
Зарезервированный флаг |
Столбец “Тип флага” несёт следующий смысл:
- “Состояние” означает, что флаг несёт в себе информацию о состоянии процессора после последней операции;
- “Управление” означает, что флаг используется для управления выполнением определенных инструкций;
- “Системный” означает, что флаг используется ядром операционной системы для своих нужд;
- “Резерв” означает, что флаг на данный момент не используется.
Рассмотрим сценарии, при котором устанавливаются некоторые особо важные флаги.
Флаг CF принимает значение 1 (говорят, устанавливается), если в результате арифметической операции в n+1-ом бите результата должна была бы появиться единица. Если такое условие не выполняется, флаг принимает значение 0. Возьмем для примера операцию сложения 8-битных чисел:
FA16 + 116 = FB16
111110102 + 000000012 = 111110112
При данной операции воображаемый 9-ый бит числа остался равен 0, поэтому флаг
CF так же станет равным 0 после выполнения инструкции add
над этими
значениями. Теперь сложим другие два числа:
FF16 + 116 = 0016
111111112 + 000000012 = 000000002
В результате сложения этих чисел получается 0, т.к. 8-битное число не может содержать значения больше 255 (FF), но если бы это число имело дополнительный 9-ый бит, он стал бы равен 1:
0111111112 + 0000000012 = 1000000002
Роль этого дополнительного бита и играет флаг CF: в данном случае он примет значение 1.
Флаг PF устанавливается, если младший значащий байт результата содержит чётное число установленных битов (или, что то же самое, если сумма всех бит - четная). Изначально этот флаг был ориентирован на использование в коммуникационных программах: при передаче данных по линиям связи для контроля мог также передаваться бит чётности и инструкции для проверки флага чётности облегчали проверку целостности данных. В настоящее время используется компиляторами в некоторых микрооптимизациях.
Флаг AF устанавливается, если в результате операции произошел перенос из младшей половины байта в старшую (либо заем из старшей в младшую). Как и PF, этот флаг работает только с младшим байтом результата. AF используется при работе с двоично-десятичными числами (binary coded decimals).
Флаг ZF называется флагом нуля и устанавливается, если результат последней операции равен нулю. Является одним из важнейших флагов, используемых в операторах ветвления.
Флаг знака, SF, устанавливается, когда старший бит результата операции принимает значение 1. Если интерпретировать результат операции как число со знаком, SF является индикатором наличия минуса.
Флаг TF используется отладчиком для исполнения инструкций пошагово.
Флаг IF определяет, должен ли процессор обрабатывать аппаратные прерывания.
Флаг направления DF определяет направление движения по буферу данных при
использовании инструкций работы со строками, например movs*
. Эти инструкции
будут подробно разобраны ниже.
Наконец, флаг OF устанавливается, если в результат операции, интерпретируемый как знаковое число, вышел за пределы области возможных значений, т.е. переполнился. Примером такой операции может служить сложение однобайтовых чисел 127 и 1. Т.к. область возможных значений однобайтового целого знакового числа составляет [-128,127], то увеличение 127 на единицу приведет к переполнению, и результат станет равен -128, а флаг CF установится в 1.
Инструкции, реализующие операцию ветвления
Простейшая инструкция, изменяющая порядок выполнения команд - инструкция JMP:
; код
... label: ; метка перехода
; код
... JMP label ; переводит управление на метку label
jmp
принимает в качестве операнда адрес, на который необходимо совершить
переход. При написании программы на ассемблере не обязательно вычислять адреса
тех или иных участков кода. В примере выше мы использовали синтаксис объявления
меток, которые так же могут фигурировать в качестве операндов. При компиляции,
ассемблер вычислит настоящее значение адреса label, и подставит это значение в
инструкцию jmp
.
Существуют различные вариации инструкции jmp
, которые осуществляют переход в
зависимости от значения флаговых регистров, описанных выше. Чтобы флаговые
регистры приняли актуальные значения, необходимо сначала выполнить одну из
инструкций сравнения:
test операнд1, операнд2
. Производит побитовое логическое “И” между операндами, и на основании результата устанавливает следующие флаги:- SF = старший бит результата;
- ZF = 1, если результат равен 0, иначе - ZF = 0;
- PF по описанному выше правилу;
- CF = 0;
- OF = 0;
cmp операнд1, операнд2
. Производит сравнение операндов путем вычитанияоперанда2
изоперанда1
с учетом знака. В зависимости от результата, регистровые флаги устанавливаются аналогично инструкции TEST.
На основании измененных значений флагов регистров можно управлять ходом выполнения программы с помощью следующих инструкций:
jz
. Осуществляет переход если ZF=1;jnz
. Осуществляет переход если ZF=0;je/jne
. Здесь “e” означает “equals”, “равно”, а “ne” – “not equals”, “не равно”, т.е. инструкцииje
иjne
осуществляют переход если операнды были равны, либо не равны друг другу, соответственно. Являются синонимами дляjz/jne
, т.к. проверка на равенство нулю и проверка на равенство двух чисел между собой суть одни и те же операции.jb/jnb
. “b” означает “below”, “ниже”.jb
совершает переход, если CF=0, или (что то же самое) если первый операнд меньше второго при интерпретации их как беззнаковые числа.jnb
совершает переход в противном случае.jc/jnc
. Синоним дляjb/jnb
. “c” в имени инструкции ссылается на имя флага CF, в зависимости от значения которого происходит переход.jae/jnae
. Противоположные инструкции кjb/jnb
. “ae” означает “above or equal”, “выше или равно”. Таким образом,jae
является синонимом дляjnb
, аjnae
- дляjb
.jbe/ja
. “be” означает “below or equal”, “ниже либо равно”.jbe
совершает переход если CF=1, или ZF=1, что соответствует ситуации, когда первый операнд меньше либо равен второму при интерпретации их как беззнаковые числа.ja
осуществляет переход в противоположном случае.jnbe/jna
. Противоположные инструкции кjbe/ja
. Т.е.jnbe
- синонимja
, аjna
- синонимjbe
.jl/jge
. “l” означает “less”, “меньше”. Отличается отjb
тем, что операнды интерпретируются как знаковые числа, т.е. при сравнении 0x7F и 0x80,jb
произведет переход, аjl
– нет (т.к. 0x80 равняется -128 в знаковой интерпретации и 128 в беззнаковой). С т.з. флаговых регистров,jl
осуществляет переход, когда SF≠OF, аjge
– в обратном случае.jnl/jnge
. Противоположные инструкции кjl/jge
. Т.е.jnl
- синонимjge
, аjnge
- синонимjl
.jle/jg
. “le” означает “less or equal”, “меньше либо равно”.jle
осуществляет переход, когда ZF=1, либо SF≠OF. Другими словами, переходjle
выполняется, если первый операнд меньше либо равен второго при знаковом сравнении.jnle/jng
. Противоположные инструкции кjle/jg
. Синонимы дляjg/jle
, соответственно.jo/jno
. Аналогичноjz/jnz
, но для флага OF.js/jns
. Аналогичноjz/jnz
, но для флага SF.jp/jnp
. Аналогичноjz/jnz
, но для флага PF.jpe/jpo
. Синоним дляjp/jnp
.jcxz/jecxz
. Осуществляет переход если CX=0 и ECX=0, соответственно.
Таким образом, с помощью комбинации инструкции сравнения и инструкции перехода можно реализовать любую необходимую операцию ветвления.
Организация циклов
Циклические конструкции в коде могут быть реализованы с помощью описанных выше инструкций условного перехода. Однако, существуют несколько специализированных инструкций для более удобной организации простых циклов:
loop метка
– уменьшает значение регистра ECX на единицу и переходит на указанную метку. Однако если в результате вычитания единицы регистр принял значение 0, то переход не осуществляется, а выполняется следующая заloop
инструкция (выход из цикла).loope метка
– работает аналогичноloop
, кроме того, что условием перехода является и ECX≠0, и ZF=1.loopz метка
– синоним дляloope
.loopne метка
– аналогloope
с обратным вторым условием, т.е. ZF=0.loopnz метка
– синоним дляloopne
.
Приведем пример кода, содержащего цикл с 5 итерациями, использующего инструкцию
loop
:
mov ecx, 5 ; задаем число итераций
loop_start: ; метка начала цикла
; код, который будет выполнен 5 раз
... loop loop_start
Инструкции для работы со строками и префиксы операций
Архитектура x86 имеет специальные инструкции, предназначенные для побайтового
сравнения строковых данных. Перед их использованием в регистры ESI и EDI
загружаются адреса сравниваемых строк, а затем вызывается одна из инструкций
семейства cmps*
:
cmpsb
. Сравнивает байт по адресу ESI с байтом по адресу EDI и устанавливает флаги регистра FLAGS по тому же принципу, как и инструкцияcmp
. После сравнения увеличивает значения регистров ESI и EDI на 1, если DF=0, или уменьшает их на 1 в обратном случае.cmpsw
. Аналог инструкцииcmpsb
, оперирующий двухбайтовыми значениями. Изменяет значения индексных регистров на 2.cmpsd
. Аналог инструкцииcmpsb
, оперирующий четырехбайтовыми значениями. Изменяет значения регистров на 4.
Благодаря тому, что инструкции cmps*
изменяют индексные регистры,
последовательность одинаковых инструкций (например, несколько cmpsb
подряд)
позволяет сравнивать строки произвольной, однако фиксированной длины.
Для работы со строками, длина которых неизвестна заранее, к строковым
инструкциям можно добавлять префиксы повтора. Строковая инструкция с
префиксом всегда уменьшает регистр ECX на единицу при каждом своем
выполнении. Префикс repe
перед строковой инструкцией означает, что она будет
выполняться не единожды, а до тех пор, пока не станет истинным одно из условий:
- Регистр ECX стал равен 0.
- Флаг ZF стал равен 0.
Префикс repz
является синонимом для repe
. Для префиксов
repne
/repnz
второе условие выхода обращается, т.е. repne
прекращает
циклическое выполнение инструкции при ZF=1. Ниже приведен код, побайтово
сравнивающий две строки не длиннее, чем 5 символов:
mov esi, string1 ; загружаем указатели на строки в регистры
mov edi, string2
mov ecx, 5 ; задаем, сколько байт будем сравнивать
repe cmpsb ; повторяем сравнение байтов
cmp ecx, 0 ; если ECX = 0, это значит что все 5 символов совпадают
jz equal ; строки равны
Для побайтового копирования строковых данных существует семейство инструкций
movs*
:
movsb
. Копирует байтовое значение из адреса ESI в адрес EDI. Уменьшает или увеличивает индексные регистры на 1, в зависимости от значения флага DF.movsw
. Аналог инструкцииmovsb
, оперирующий двухбайтовыми значениями.movsd
. Аналог инструкцииmovsb
, оперирующий двухбайтовыми значениями.
Для повторения выполнения инструкций MOVS используется префикс REP.
Инструкции scasb
, scasw
, scasd
сравнивают значение по адресу из
регистра EDI со значением в регистре EAX. К ним так же применимы
префиксы repe
/repne
.
Контрольные вопросы
- Флаги регистра FLAGS и их назначение.
- Инструкции условного перехода.
- Инструкции для работы со строками.
- Префиксы строковых инструкций.
Задание на лабораторную работу
Написать, скомпилировать программу, соответствующую своему варианту, и убедиться в её корректном функционировании с помощью отладчика. Размерности массивов и матриц должны задаваться в программе с помощью переменных. При организации циклов необходимо использовать эти переменные в качестве индексов при доступе к элементам массивов/матриц.
Варианты заданий
Вариант 1
Даны две строки - с произвольным текстом и символами-разделителями. Разбить первую строку на подстроки, используя в качестве разделителей каждый из символов второй.
Вариант 2
Дана матрица произвольной размерности. Определить минимум среди максимальных значений по строкам.
Вариант 3
Дана строка. Определить наиболее длинную последовательность одинаковых символов, входящих в нее.
Вариант 4
Дана последовательность байт. Реализовать сортировку пузырьком, интерпретируя элементы вектора как знаковые числа.
Вариант 5
Дана матрица произвольной размерности. Реализовать удаление (затирание нулями) повторяющихся строк.
Вариант 6
Дана последовательность чисел. Для каждого числа из последовательности сформировать ряд, каждый элемент которого вычисляется по формуле Фибонначи, начиная с этого числа. Полученные ряды образуют матрицу, в которой число строк равно длине исходной последовательности, а число столбцов может быть произвольным.
Вариант 7
Дана матрица. Составить вектор, копируя из первой строки первое число, из второй - второе, и т.д. Останавливаться при исчерпании строк или столбцов.
Вариант 8
Дана матрица. Заменить отрицательные числа значением -1, положительные - значением 1.
Вариант 9
Дан вектор. Составить матрицу заданной размерности из элементов вектора. При исчерпании элементов вектора, продолжать заполнение матрицы с начала вектора.
Вариант 10
Дана строка. Посчитать частоту вхождения в неё каждого символа.
Вариант 11
Дана матрица. Посчитать среднее значение для каждого столбца и составить из них вектор.
Вариант 12
Дана матрица. Написать код её транспонирования.
Вариант 13
Дана матрица. Построчно скопировать ее содержимое в одномерный массив.
