Jle ассемблер что это

от admin

Jle ассемблер что это

На этом шаге мы рассмотрим переходы для знаковых данных .


    JG/JNLE — переход, если больше/переход, если не меньше и не равно . Команды передают управление по адресу, при условии одновременного равенства значений флагов SF и OF и равенства нулю флага ZF . В противном случае выполняется следующая по очереди команда.

Пример. Пусть регистр SI содержит число 0020H, регистр DX — число FF23H и выполняется следующая последовательность команд:

После выполнения первой команды все три флага SF, OF и ZF сбрасываются в нуль. После выполнения команды JG SAME выполняется команда MOV BX, 0 . Это означает, что содержимое регистра SI (положительное шестнадцатеричное число 20H) больше, чем содержимое регистра DX (отрицательное шестнадцатеричное число FF23H).

Пример. Пусть регистр AX содержит число 0020H и выполняется следующая последовательность команд:

После выполнения первой команды в регистре AX будет находиться положительное, отличное от нуля число. Следовательно, флаги SF и OF будут сброшены в 0. После выполнения команды JGE UNSIGN управление будет передано по адресу UNSIGN . Это означает, что содержимое регистра AX не меньше нуля.

Пусть регистр AX содержит число FF00H. Тогда в приведенном примере после выполнения первой команды будет установлен в 1 флаг SF и будет выполнена команда ADD BX,1 .

Пример. Пусть регистр AX содержит число FFFFH, регистр DX — число 0002H и выполняется следующая последовательность команд:

Первая команда устанавливает флаг SF в 1, а флаг OF в 0. После выполнения команды JL CHECK управление будет передано на метку CHECK . Это означает, что содержимое регистра AX меньше содержимого регистра DX .

Пусть регистр AX содержит число FF00H. Тогда в приведенном примере после выполнения первой команды будет установлен в 1 флаг SF и будет выполнена команда ADD BX,1 .

Пример. Пусть регистр CX содержит число D700H, а регистр AX — число 0124H и выполняется следующая последовательность команд:

После выполнения первой команды флаг SF будет установлен в 1, а OF — в 0. После выполнения команды JLE NEXT управление будет передано по адресу NEXT . Это означает, что содержимое регистра CX меньше AX .

УСЛОВНЫЕ КОМАНДЫ

Условная команда — это такая команда, выполнение которой зависит от каких-либо условий. Например, требуется определенная установка флажков или необходимо наличие нуля в некотором регистре. Самыми употребительными из всех условных команд являются команды условной передачи управления.

Команды JE, JNE, JA, JB, . (и т.д.) — условная передача управления (переход, если условие удовлетворяется).

Команды SETE, SETNE, SETA, SETB, . (и т.д.) — установка байта по условию.

Команды LOOP, LOOPE/LOOPZ, LOOPNE/LOOPNZ — переход для повторения цикла.

Команды JCXZ, JECXZ — переход, если значение счетчика равно нулю.

Команды условной передачи управления

Если условие истинно (TRUE), то происходит переход по указанному адресу, иначе (FALSE) переход не происходит и выполняется следующая по порядку команда.

Для перехода задается относительный адрес, то есть, смещение относительно адреса команды, следующей после данной команды. Задаваемый на ассемблере адрес может иметь тип SHORT или тип NEAR. Адрес типа SHORT кодируется в команде всего одним байтом. Поэтому для адреса типа SHORT переход может быть только в пределах . Адрес типа NEAR кодируется двумя байтами в режиме 16 бит и четырьмя байтами в режиме 32 бита.

Всего есть 16 разных условий, им соответствует 16 разных кодов команд на уровне процессора. Но на уровне языка ассемблера эти условия превращаются в 30 разных команд. Таким образом, одна и та же команда процессора может иметь разные имена (синонимы) на языке ассемблера.

Кодирование

Команда (asm) Тип адреса Код Формат
J(cond) SHORT 70 +cond —-cond addr(1)
J(cond) NEAR 0F 80 +cond —-cond addr(2/4)

Примечание. Код условия (cond) может иметь значение от 0 до F (hex).

Условия при сравнении чисел без знака

Код
(cond)
Команда
(asm)
Флажки
Условие
(eng)
Условие
(рус)
0100 JE ZF=1 if equalесли равно
0101 JNE ZF=0 if not equalесли не равно
0010
JB
/JNAE
CF=1
if below
/not above or equal
если меньше
/не больше или равно
0011
JAE
/JNB
CF=0
if above or equal
/not below
если больше или равно
/не меньше
0110
JBE
/JNA
CF=1
or ZF=1
if below or equal
/not above
если меньше или равно
/не больше
0111
JA
/JNBE
CF=0
and ZF=0
if above
/not below or equal
если больше
/не меньше или равно

Условия при сравнении чисел со знаком

Код
(cond)
Команда
(asm)
Флажки
Условие
(eng)
Условие
(рус)
0100 JE ZF=1 if equalесли равно
0101 JNE ZF=0 if not equalесли не равно
1100
JL
/JNGE
SF!=OF
if less
/not greater or equal
если меньше
/не больше или равно
1101
JGE
/JNL
SF=OF
if greater or equal
/not less
если больше или равно
/не меньше
1110
JLE
/JNG
ZF=1
or SF!=OF
if less or equal
/not greater
если меньше или равно
/не больше
1111
JG
/JNLE
ZF=0
or SF=OF
if greater
/not less or equal
если больше
/не меньше или равно

Условия по отдельным флажкам

Код
(cond)
Команда
(asm)
Флажки
Условие
(eng)
Условие
(рус)
0100 JZ ZF=1 if zeroесли есть флаг нуля
0101 JNZ ZF=0 if not zeroесли нет флага нуля
1000 JS SF=1 if signесли есть флаг знака
1001 JNS SF=0 if not signесли нет флага знака
0010 JC CF=1 if carryесли есть флаг переноса
0011 JNC CF=0 if not carryесли нет флага переноса
0000 JO OF=1 if overflowесли есть флаг переполнения
0001 JNO OF=0 if not overflowесли нет флага переполнения
1010
JP
/JPE
PF=1
if parity
/parity even
если есть флаг четности
/четный
1011
JNP
/JPO
PF=0
if not parity
/parity odd
если нет флага четности
/нечетный

Все условия, упорядоченные по кодам (cond)
и соответствующие коды операций

В этой таблице показаны также и соответствующие коды операций для команд с адресом типа SHORT и для команд с адресом типа NEAR.

Код для
SHORT
Код для
NEAR
Код
(cond)
Команда
(asm)
Флажки
70 0F 80 0000 JO OF=1
71 0F 81 0001 JNO OF=0
72 0F 82 0010 JB/JNAE/JC CF=1
73 0F 83 0011 JAE/JNB/JNC CF=0
74 0F 84 0100 JE/JZ ZF=1
75 0F 85 0101 JNE/JNZ ZF=0
76 0F 86 0110 JBE/JNA CF=1 or ZF=1
77 0F 87 0111 JA/JNBE CF=0 and ZF=0
78 0F 88 1000 JS SF=1
79 0F 89 1001 JNS SF=0
7A 0F 8A 1010 JP/JPE PF=1
7B 0F 8B 1011 JNP/JPO PF=0
7C 0F 8C 1100 JL/JNGE SF!=OF
7D 0F 8D 1101 JGE/JNL SF=OF
7E 0F 8E 1110 JLE/JNG ZF=1 or SF!=OF
7F 0F 8F 1111 JG/JNLE ZF=0 or SF=OF

Команда на языке ассемблера

Форматы Примеры
J(cond) label
JNBE Lab02
JZ SHORT metka
JC NEAR met44

Команды установки байта по условию

SETE, SETNE, SETA, SETB, .

В команде установки байта SET(cond) используется тот же самый набор из 16 условий, что и в команде условной передачи управления J(cond) . И в мнемонике на языке ассемблера эти условия представляются такими же 30-ю способами.

На языке ассемблера команда имеет один операнд. Это может быть либо 8-битный регистр, либо адрес в памяти.

Команда заносит в байт, указанный в операнде, значение 1, если условие выполняется (TRUE), и значение 0 в противном случае (FALSE).

Кодирование

Команда (asm) Код Формат
SET(cond) 0F 90 +cond /000 —-cond NNN

Примечания.
1. Код условия (cond) может иметь значение от 0 до F (hex).
2. В байте (mod,reg,r/m) трехбитное поле reg должно быть равно 000.

Команда на языке ассемблера

Форматы Примеры
SET(cond) reg8
SETZ AL
SETNAE DH
SET(cond) mem8
SETNGE name
SETO BYTE PTR [BX]
SETC array [EDI]

Команда перехода для повторения цикла

LOOP, LOOPE/LOOPZ, LOOPNE/LOOPNZ

Эти команды используются для управления циклом со счетчиком в регистре CX (или в регистре ECX ).

При выполнении команды сначала происходит декремент (уменьшение на единицу) счетчика в регистре. (При этом никакие флажки не изменяются.) В дальнейшем, при проверке условий, действует уже измененное значение счетчика.

Если атрибут размера адреса равен 16 битам, то счетчиком служит регистр CX , если этот атрибут равен 32 битам, то регистр ECX .

Если условия (см. в таблице ниже) выполняются, то происходит переход по указанному адресу.

В условиях, кроме значения счетчика, учитывается еще и флажок нуля. Значение флажков вырабатываются командами, предшествующими команде LOOP.

Для перехода задается адрес типа SHORT, то есть, относительный адрес из одного байта. Поэтому переход может быть в пределах от (-128) до (+127) относительно адреса команды, следующей после команды LOOP.

Кодирование

См. в таблице ниже.

Команда перехода по счетчику

Переход по указанному адресу происходит в том случае, если значение счетчика равно нулю. Счетчиком служит регистр CX , если установлен режим 16 бит, или регистр ECX , если режим 32 бита.

Для перехода задается адрес типа SHORT, то есть, относительный адрес из одного байта. Поэтому переход может быть в пределах от (-128) до (+127) относительно адреса команды, следующей после команды JCXZ.

Данная команда не изменяет содержимое регистра CX (или ECX ) и не изменяет флажки.

Обычно процессор выполняет команду JCXZ дольше, чем вот такие две команды: команда сравнения содержимого регистра с нулем плюс команда условного перехода по флажку. По этой причине команда JCXZ обычно заменяется на другие команды.

Постигаем Си глубже, используя ассемблер. Часть 2 (условия)

Вот и вторая часть часть цикла. В ней мы будем разбирать условия. В этот раз попробуем другие уровни оптимизации, и посмотрим, как это может повлиять на код.

Стоит указать цель этих статей, чтобы не было недопонимания. Я не буду разбирать каждый компилятор Си в отдельности. Это долго и нудно. Вместо этого, я хочу увлечь читателей разбором интересных интерпретаций Си-кода, чтобы люди осознавали, как их код может меняться и исполняться процессором. А так же развеять некоторые мифы, которые ходят среди начинающих программистов. Например, есть, правда, те, кто считает, что если складывать числа в цикле, то это будет быстрее, чем просто умножить одно число на другое. Статья не разбирает конкретно gcc с -m32 -O0, некоторые не совсем поняли идею. Если будет реальный смысл, то я поменяю и компилятор, и ключи.

Т. е. что я хочу сказать? Рассмотрим два старых примера:

Действительно, clang в первом случае определяет переменную в стек, но насколько это интересно или существенно для нас? Те, кто знаком со спецификатором register, читали/знают, что это, всего лишь, рекомендация. Поэтому компилятор может просто проигнорировать спецификатор. К тому же, цель примера была познакомить читателя с регистрами, взяв простейший пример. Подошел идеально для этого gcc. Второй пример еще проще, в нем clang сразу же делает сдвиг, и сдается уже при умножении на 3, выдавая imul. Честно, не очень понимаю, что в данном примере любопытного, поэтому также привел код для gcc, который извращается до числа 22. Мы все и так знаем, что в стандарте языка не прописано, как реализовывать ту или иную вещь. И разработчики компилятора вольны делать свои реализации, лишь бы они не нарушали стандарт. Поэтому и имеем разную интерпретацию кода в зависимости от компилятора. Но, простите меня, разбирать каждую? В чем практичность данного материала? Заморочить всем голову? Как правильно было замечено, если интересует конкретный компилятор, то можно просто посидеть с отладчиком. И это уже не будет так страшно для тех, кто читает эти статьи.

Простейшее условие

Для начала сравним переменную и число:

В первой строке, компилятор добавляет в стек значение переменной «а». Во второй у нас новая инструкция cmp. Не трудно догадаться, что эта инструкция сравнивает два значения. В нашем случае: значение из стека и 4.

Но как она работает? Просто отнимает от первого операнда второй. Похожим образом работает и инструкция sub, но в случае с cmp результат не сохраняется. Однако флаги в регистре EFLAGS/RFLAGS устанавливаются в соответствии с этим результатом. Не вдаваясь в подробности, мы можем узнать был ли положительный результат, отрицательный или ноль. Как раз следующая команда условного перехода jg срабатывает в том случае, если результат был положительным (jump if greater).

Если вы это осмыслили, то мог возникнуть справедливый вопрос: а почему больше, если знак был меньше? Действительно, написали мы если а < 5, но это превратилось в делай что-то, если а > 4. Но логика программы не нарушилась. Ведь, если a > 4, то происходит return 0. Тут может возникнуть еще один справедливый вопрос, а если написать условие: if (a > 4) return 0, как изменится сам код?

И мы снова получаем разворот условия: jle, как вы догадались, меньше либо равно (jump if less or equal)

Тут все дело в том, что return завершает программу, поэтому нужно еще выполнить две последние инструкции, именно поэтому строчка jmp .L3 не меняется в обоих примерах. Это инструкция безусловного перехода. В нашем случае: она пропускает строчку следующую за условием, где в регистр eax должно записаться совсем другое число.

Т. е. компилятор проверяет обратное условие, чтобы при оригинальном false отослать по коду вниз, но если оригинальное условие – true, то выполняется код, который идет непосредственно за cmp и условным переходом. Давайте для наглядности отметим цифрами ветки условия:

Как видите, структура программы не нарушается, но если мы заменим условный переход, то:

Т. е. внутренняя часть условия падает вниз программы, что не очень хорошо: допустим, после условия (в секции #1) есть еще куча строчек, тогда, чтобы увидеть секцию #0, мы будем крутить листинг очень далеко вниз. (Забыл уточнить, что затем надо будет еще и вернуться для продолжения выполнения кода после if. Т. е. еще одна метка и еще один переход.)

unsigned

Мы только что рассмотрели сравнение чисел, имеющих знак (signed), а что если мы будем сравнивать беззнаковые числа (unsigned)?

Ничего не изменилось, кроме инструкции условного перехода: вместо jle теперь jbe (jump below or equal). Зачем две различные инструкции для сравнения знаковых и беззнаковых чисел?

Хотя по факту, в памяти, все равно, будет 4294967295. Это всего лишь способ отображения, вы можете написать в Си:

Но при инструкции cmp устанавливается не один флаг, а несколько. Инструкция jbe проверяет флаг переполнения при вычитании, а jle проверяет флаг, который равен значению старшего бита результата (т. е. при отрицательном результате там 1). В реальности все чуточку сложнее: JBE (CF=1 or ZF=1), JLE (ZF=1 or SF<>OF), но нам можно на этом не зацикливаться. Перейдем к более интересным вещам:

будет преобразовано в:

Здорово, правда? По логике нашего кода переменная «a» не будет никогда меньше нуля, поэтому условие можно просто выкинуть.

Инструкция je осуществляет переход, если результат сравнения равен нулю (jump if equal).

”<” быстрее, чем ”<=”? Или чем ”< || =”?

Мы уже рассмотрели несколько инструкций условного перехода: jle, jbe, jg и je. Таких инструкций немногим больше для всех случаев, также есть и обратные: например jne – не нуль или не равно или jnbe – не ниже и не равно. Т. е. для любого сравнения чисел мы получим две инструкции cmp (или test) и jcc (условный переход). Таким образом можно сделать вывод, что, например, нет разницы в количестве инструкций для < и <=.

разница будет, но только при -O0.

Давайте взглянем на следующую программу:

На этот раз я использую clang 5.0.0 -O3 -m32, так как генерируется меньше asm кода, и по этому примеру будет легче объяснить, что происходит:

#1: сравнение переменной «а» с нулем
#2: в регистре ecx теперь 10
#3: в регистре eax теперь 20
#4: cmovle аналогичен jle, только он перемещает значение при условии. Таким образом, если a<=0, то в eax попадает значение из ecx (10), иначе, просто, останется 20.

Вы уже понимаете, что если в Си-коде заменить на a<=0, то ничего не изменится, но можете проверить, если есть желание.

Когда условия перестают быть условиями

Представьте ситуацию: в вашем коде есть условие, но при отладке вы не можете найти условные инструкции. Интересно?

Взглянем на следующий код:

Вы могли бы ожидать cmp и метки, могли ожидать даже более нетривиальные вещи, как setx, но получили следующее (clang 5.0.0 -O3 -m32):

Ну, и что это? Давайте разберемся. С первой строкой все понятно: в eax перенесли значение переменной x.

Следующую строчку вы должны помнить по прошлой статье. Это сдвиг вправо на 31 бит. Т. е. по факту у нас остается только первый бит от всего числа.

Далее идет побитовая операция «или». Т. е. у нас получается в результате или 10, или 11 (в двоичной системе счисления). На этом все, следующие строки относятся к эпилогу функции.

Что интересно, догадаться написать такой код, особо труда не составляет. Мы же просто прибавляем к двойке знак числа, находящийся в переменной x.

По этой же логике, но немного не так действует, например, gcc 4.8.5:

sar — это тоже сдвиг вправо, но работает он несколько иначе, самый старший бит, т. е. знак, он не сдвигает.

[1000] shr [0100] shr [0010] shr [0001]

[1000] sar [1100] sar [1110] sar [1111]

Т. е. если у нас будут все единицы, то число было отрицательным, мы инвертируем все биты, получаем нуль, добавляем 3, ровно то, что мы хотели. А если число положительное, то будут все нули, после инвертирования станут единицами. По факту — это -1, добавив к нему 3, получим 2.

MSVC -O2 при этом поступает более ожидаемо:

Очень кратко: младший байт регистра eax выставляется в единицу при условии, что результат сравнения оказался отрицательным, затем добавляется 2. Это классика. Компиляторы очень любят данный прием, надеюсь, мы его еще повстречаем.

Оператор else

Я думаю, все понимают, что для else не проверяется обратное условие. В коде просто появляется еще одна метка, вот и все особенности. Давайте убедимся в этом и рассмотрим следующий код:

Как видите, отличие только в том, что после выполнения кода внутри if, мы перескакиваем через внутренности блока else (метка .L2). Надеюсь, подробный анализ не нужен, вроде бы, все очевидно.

Логические операции в if

Давайте посмотрим на немного нестандартный пример:

Сначала попробуйте дать ответ: какое значение будет в переменной «a»? Если для вас это было не сложно, то вы уже примерно представляете, как будет выглядеть asm код.

Итак, пробуем разобраться. Я выделил операции номерами, чтобы проще было различать, что к чему относится.

#1: a++ < 0. При этом в eax записывается значение до инкремента, именно его и нужно сравнить с нулем. Инструкция test работает по принципу and, но не изменяет сами операнды, только флаги. В нашем случае после test мы проверяем знак числа, если он равен 1, то осуществляем переход. Также в стек возвращается значение, увеличенное на единицу. Увеличение делается инструкцией lea edx, [eax + 1]. Инструкция lea используется для загрузки эффективного адреса. В нашем случае, она заменяет сразу две инструкции: mov edx, eax и add edx, 1.
#2: a++ > 5. Фактически, происходит тоже самое, только переход на метку .L3 в случае, если a <= 5. Т. е. на метку .L2 мы попадаем, если выполняется первое условие или второе. При этом обратите внимание, что второе условие не будет вычисляться, если выполнилось первое. Но вы это и так должны были знать.
#3: В регистре eax оказывается 1
#4: В регистре eax оказывается 0
#5: Проверяем младший байт регистра eax на нуль, если равен, то переходим на метку .L5
#6: Иначе добавляем в переменную «a» 1. Переходим в конец программы.
#7: Если младший байт в eax был равен нулю, то добавляем переменной «а» 2

Т. е. #3 отвечает за то, что оба условия выполнены и ставится «флаг», затем осуществляется проверка в #5, если «флаг» стоит, то добавили 1, иначе 2.

Стоит также отметить, что при if (a < 0 && a < -5), если первое условие не выполняется, то второе также не будет вычисляться.

Заключение

Мы кратко рассмотрели условия в Си. Вы увидели, что компилятор может, как слегка модифицировать код, так и менять его до неузнаваемости при оптимизации.

К сожалению, статья вышла настолько длинной, что не удалось рассмотреть оператор switch, поэтому, если хотите, можно это сделать в следующей статье. Заодно, можно рассмотреть и оптимизированные программы с else.

Assembler:Commands:JLE

Jumps if less or equal (ZF=1 or SF!=OF) to the given operand (address).

Jcc Checks the state of one or more of the status flags in the EFLAGS register (CF, OF, PF, SF, and ZF) and, if the flags are in the specified state (condition), performs a jump to the target instruction specified by the destination operand. A condition code (cc) is associated with each instruction to indicate the condition being tested for. If the condition is not satisfied, the jump is not performed and execution continues with the instruction following the Jcc instruction.

The target instruction is specified with a relative offset (a signed offset relative to the current value of the instruction pointer in the EIP register). A relative offset (rel8, rel16, or rel32) is generally specified as a label in assembly code, but at the machine code level, it is encoded as a signed, 8-bit or 32-bit immediate value, which is added to the instruction pointer. Instruction coding is most efficient for offsets of -128 to +127. If the operand-size attribute is 16, the upper two bytes of the EIP register are cleared, resulting in a maximum instruction pointer size of 16 bits.

The conditions for each Jcc mnemonic are given in the «» column of the table on the preceding page. The terms «less» and «greater» are used for comparisons of signed integers and the terms «above» and «below» are used for unsigned integers.

Because a particular state of the status flags can sometimes be interpreted in two ways, two mnemonics are defined for some opcodes. For example, the JA (jump if above) instruction and the JNBE (jump if not below or equal) instruction are alternate mnemonics for the opcode 77H.

The Jcc instruction does not support far jumps (jumps to other code segments). When the target for the conditional jump is in a different segment, use the opposite condition from the condition being tested for the Jcc instruction, and then access the target with an unconditional far jump (JMP instruction) to the other segment. For example, the following conditional far jump is illegal: JZ FARLABEL; To accomplish this far jump, use the following two instructions: JNZ BEYOND; JMP FARLABEL; BEYOND: The JECXZ and JCXZ instructions differ from the other Jcc instructions because they do not check the status flags. Instead they check the contents of the ECX and CX registers, respectively, for 0. Either the CX or ECX register is chosen according to the address-size attribute.

These instructions are useful at the beginning of a conditional loop that terminates with a conditional loop instruction (such as LOOPNE). They prevent entering the loop when the ECX or CX register is equal to 0, which would cause the loop to execute 232 or 64K times, respectively, instead of zero times.

All conditional jumps are converted to code fetches of one or two cache lines, regardless of jump address or cacheability.

This instruction can be used to execute four different types of jumps:

Near jump A jump to an instruction within the current code segment (the segment currently pointed to by the CS register), sometimes referred to as an intrasegment jump. Short jump A near jump where the jump range is limited to -128 to +127 from the current EIP value. Far jump A jump to an instruction located in a different segment than the current code segment but at the same privilege level, sometimes referred to as an intersegment jump. Task switch A jump to an instruction located in a different task.

Читать:
Как в ворде разделить столбец в таблице на 2 столбика

Похожие статьи