Кроме инструкции безусловного перехода JMP в ассемблере GAS есть ряд инструкций, которые выполняют условные переходы в зависимости от установки битов в регистре флагов EFLAGS. В частности, в этом регистре учитываются четыре бита, которые еще называют флагами состояния, - carry (флаг переноса), overflow (флаг переполнения), sign (флаг знака) и zero (флаг нуля):
CF (флаг переноса): устанавливается, если происходит беззнаковое переполнение, то есть при сумме с переносом или вычитании с заимствованием (например, при сумме чисел 0FFh и 01h). Если переполнение не происходит, то флаг не устанавливается.
OF (флаг переполнения): установливается, если происходит переполнение со знаком, а именно когда переполняется бит, следующий за старшим
знаковым битом. Например, при сумме чисел 7Fh и 01h. В двоичной системе это будет операция 0111 1111 + 0000 0001
, результатом которой формально
будет число 1000 0000
. Таким образом, произошло изменение старшего знакового бита.
SF (флаг знака): устанавливается, если старший бит результата установлен. В противном случае флаг знака сброшен (то есть флаг знака отражает состояние старшего бита результата).
ZF (флаг нуля): устанавливается, если результат вычисления дает 0. Если результат ненулевой, флаг сброшен
Эти флаги устанавливаются в результате различных операций. В частности, инструкции add, sub, and, or, xor и not влияют на установку флагов. А mov или lea не влияют.
Для проверки этих флагов и выполнения условного перехода в ассемблере есть следующие инструкции:
jc: выполняет переход к метке, если флаг переноса установлен
jnc: выполняет переход к метке, если флаг переноса НЕ установлен
jo: выполняет переход к метке, если флаг переполнения установлен
jno: выполняет переход к метке, если флаг переполнения не установлен
js: выполняет переход к метке, если флаг знака установлен
jns: выполняет переход к метке, если флаг знака не установлен
jz: выполняет переход к метке, если флаг нуля установлен
jnz: выполняет переход к метке, если флаг нуля не установлен
Все эти инструкции принимают один операнд - метку, к которой выполняется переход. Например:
.globl _start .text _start: movq $0xffffffffffffffff, %rcx movq $1, %rdx addq %rcx, %rdx # RDX = RDX + RCX jc carry_set # если флаг переноса установлен, переход к метке carry_set movq $0, %rdi # если флаг переноса не установлен, RDI = 0 jmp exit carry_set: # если флаг переноса установлен movq $1, %rdi # RDI = 1 exit: # метка exit movq $60, %rax syscall
Здесь в регистр RCX загружаем число 0xffffffffffffffff
, а в регистр RDX - число 1 Затем с помощью инструкции addq
складываем значения этих регистров:
movq $0xffffffffffffffff, %rcx movq $1, %rdx addq %rcx, %rdx # RDX = RDX + RCX
Если произошел перенос, то устанавливается флаг переноса. С помощью инструкции jc carry_set
проверям, имеет ли место перенос. И если есть перенос, и
соответственно флаг переноса (флаг CF) установлен в 1, то выполняем переход к метке carry_set. Если флаг переноса НЕ установлен,
то выполняем последующие инструкции - в регистр RDI помещаем число 0 и переходим к метке exit:
movq $0, %rdi # если флаг переноса не установлен, RDI = 0 jmp exit
Если флаг установлен, то переходим к метке carry_set и в регистр RDI помещаем число 1:
carry_set: # если флаг переноса установлен movq $1, %rdi # RDI = 1
В данном случае поскольку при сложении чисел 0xffffffffffffffff и 1 результат будет равен 0x1_0000000000000000
, который не помещается в один 64-разрядный регистр RDX, то
соответственно будет выполняться перенос, то будет происходить переход к метке carry_set, а в регистре RDI окажется число 1.
Подобным образом можно работать с другими инструкциями условного перехода. Например:
.globl _start .text _start: movq $5, %rcx movq $5, %rdx subq %rcx, %rdx # RDX = RDX - RCX jz zero_set # если флаг нуля установлен, переход к метке zero_set movq $2, %rdi # если флаг нуля не установлен, RDI = 2 jmp exit zero_set: # если флаг нуля установлен movq $4, %rdi # RDI = 4 exit: movq $60, %rax syscall
Здесь помещаем в регистры RDX и RCX некоторые числа и вычитаем из значения регистра RDX число регистра RCX. Если эти числа равны, то результатом будет число ноль, и будет усоановлен флаг нуля ZF. То есть флаг нуля позволяет проверить два числа на равенство при применении вычитания. И в данном случае мы проверяем установку флага нуля, и если он установлен, переходим к метке zero_set.
Используя подобные инструкции, легко создать циклические конструкции:
.globl _start .text _start: movq $5, %rcx movq $0, %rdi loop: addq $2, %rdi # RDI = RDI + 2 subq $1, %rcx # RCX = RCX - 1 jnz loop # если флаг нуля НЕ установлен, переход обратно к метке loop movq $60, %rax syscall
Здесь регистр выступает в роли счетчика. После метки loop фактически идет цикл. В цикле увеличиваем значение регистра RDI на 2. И затем уменьшаем значение регистра RCX на 1.
Далее с помощью инструкции jnz loop
проверяем флаг нуля. Если флаг нуля НЕ установлен (то есть если в RCX НЕ нулевое значение), то переходим обратно к метке loop и повторяем действия.
Если вдруг после вычитания в регистре RCX оказался ноль, то выходим из этого цикла и переходим к следующей инструкции - movq $60, %rax
Ассемблер предоставляет ряд инструкций для управления флагами состояния. Отмечу основные:
clc: сбрасывает в 0 флаг переноса (CF)
setc: устанавливает флаг переноса (CF)
lahf: копирует флаги состояния из регистра %eflags в регистр %ah
sahf: сохраняет флаги состояния из регистра %ah в регистр %eflags
Инструкции lahf и sahf применяют следующий порядок битов для флагов, начиная с младшего бита:
Флаг переноса (CF)
Всегда равен 1
Флаг паритетности (PF)
Всегда равен 0
Дополнительный флаг переноса (AF)
Всегда равен 0
Флаг нуля (ZF)
Флаг знака (SF)
Биты 1, 3, и 5 не используются.