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

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

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

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

.code
main proc
    mov eax, 0FFFFFFFFh
    mov ebx, 1
    add eax, ebx
    jc carry_set
    mov eax, 0
    ret
carry_set:
    mov eax, 1
    ret
main endp
end

Здесь в регистр EAX загружаем число 0FFFFFFFFh, а в регистр EBX - число 1. Затем с помощью инструкции add складываем значения EAX и EBX и результат помещаем в EAX.

mov eax, 0FFFFFFFFh
mov ebx, 1
add eax, ebx

Если произошел перенос, то устанавливается флаг переноса. С помощью инструкции:

jc carry_set

Проверям, имеет ли место перенос. И если есть переполнение, и соответственно флаг переноса установлен в 1, то выполняем переход к метке carry_set. Если флаг переноса НЕ установлен, то выполняем последующие инструкции - в регистр EAX помещаем число 0 и выходим из процедуры:

mov eax, 0
ret

Если флаг установлен, то переходим к метке carry_set и в регистр EAX помещаем число 1 и выходим из процедуры:

carry_set:
    mov eax, 1
    ret

В данном случае поскольку при сложении чисел 0FFFFFFFFFh и 1 результат будет равен 100000000h, соответственно будет выполняться перенос, то будет происходить переход к метке overflow, а в регистре EAX окажется число 1.

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

.code
main proc
    mov rdx, 5
    mov rcx, 5
    sub rdx, rcx
    jz zero_set
    mov eax, 2
    ret
zero_set:
    mov eax, 4
    ret
main endp
end

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

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

.code
main proc
    mov rax, 0
    mov rcx, 5
main_loop:
    add rax, 2
    sub rcx, 1
    jnz main_loop
    ret
main endp
end

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

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