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

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

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

Все эти инструкции принимают один операнд - метку, к которой выполняется переход. Например, возьмем следующую программу для Linux:

global _start

section .text
_start:
    mov al, 255
    add al, 3       ; AL = AL + 3
    jc carry_set    ; если флаг переноса установлен, переход к метке carry_set
    mov rdi, 2      ; если флаг переноса не установлен, RDI = 2
    jmp exit
carry_set:          ; если флаг переноса установлен
    mov rdi, 4      ; RDI = 4
exit:               ; метка exit
    mov rax, 60
    syscall 

Здесь в регистр AL загружаем число 255. Затем с помощью инструкции add складываем значение из регистра AL с числом 3:

mov al, 255
add al, 3       ; AL = AL + 3

Если произошел перенос, то устанавливается флаг переноса CF. С помощью инструкции jc carry_set проверяем, имеет ли место перенос. И если есть перенос, и соответственно флаг переноса (флаг CF) установлен в 1, то выполняем переход к метке carry_set, где в регистр rdi помещается число 4:

    jc carry_set    ; если флаг переноса установлен, переход к метке carry_set

....................

carry_set:          ; если флаг переноса установлен
    mov rdi, 4      ; RDI = 4

Если флаг переноса НЕ установлен, то выполняем последующие инструкции - в регистр RDI помещаем число 2 и переходим к метке exit:

mov rdi, 2      ; если флаг переноса не установлен, RDI = 2
jmp exit

В данном случае поскольку регистр AL - 8 разрядный, а максимальное 8-разрядное число - 255, то при сложении с числом 3 результат будет равен 258 или 0b1_0000_0010, который не помещается в один 8-разрядный регистр AL. Соответственно будет выполняться перенос и будет происходить переход к метке carry_set, а в регистре RDI окажется число 4.

Аналогичный пример для Windows:

global _start

section .text
_start:
    mov al, 255
    add al, 3       ; AL = AL + 3
    jc carry_set    ; если флаг переноса установлен, переход к метке carry_set
    mov rax, 2      ; если флаг переноса не установлен, RAX = 2
    jmp exit
carry_set:          ; если флаг переноса установлен
    mov rax, 4      ; RAX = 4
exit:               ; метка exit   
    ret    

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

global _start

section .text
_start:
    mov rcx, 5
    mov rdx, 5
    sub rdx, rcx     ; RDX = RDX - RCX
    jz zero_set      ; если флаг нуля установлен, переход к метке zero_set
    mov rdi, 2       ; если флаг нуля не установлен, RDI = 2
    jmp exit
zero_set:            ; если флаг нуля установлен
    mov rdi, 4       ; RDI = 4
exit:              
    mov rax, 60
    syscall 

Здесь помещаем в регистры RDX и RCX некоторые числа и вычитаем из значения регистра RDX число регистра RCX. Если эти числа равны, то результатом будет число ноль, и будет усоановлен флаг нуля ZF. То есть флаг нуля позволяет проверить два числа на равенство при применении вычитания. И в данном случае мы проверяем установку флага нуля, и если он установлен, переходим к метке zero_set.

Используя подобные инструкции, легко создать циклические конструкции:

global _start

section .text
_start:
    mov rcx, 5
    mov rdi, 0
loop:
    add rdi, 2      ; RDI = RDI + 2 
    dec rcx         ; RCX = RCX - 1 
    jnz loop        ; если флаг нуля НЕ установлен, переход обратно к метке loop         
    mov rax, 60
    syscall 

Здесь регистр выступает в роли счетчика. После метки loop фактически идет цикл. В цикле увеличиваем значение регистра RDI на 2. И затем уменьшаем значение регистра RCX на 1. Далее с помощью инструкции jnz loop проверяем флаг нуля. Если флаг нуля НЕ установлен (то есть если в RCX НЕ нулевое значение), то переходим обратно к метке loop и повторяем действия. Если вдруг после вычитания в регистре RCX оказался ноль, то выходим из этого цикла и переходим к следующей инструкции - mov rax, 60

Управление флагами

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

  • 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