Флаги состояния и условные переходы

Последнее обновление: 23.09.2023

Кроме инструкции безусловного перехода 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 применяют следующий порядок битов для флагов, начиная с младшего бита:

  1. Флаг переноса (CF)

  2. Всегда равен 1

  3. Флаг паритетности (PF)

  4. Всегда равен 0

  5. Дополнительный флаг переноса (AF)

  6. Всегда равен 0

  7. Флаг нуля (ZF)

  8. Флаг знака (SF)

Биты 1, 3, и 5 не используются.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850