Треки
/
x86-64 Assembly
x86-64 Assembly
/
Салабус
/
Умовні конструкції
Ум

Умовні конструкції у x86-64 Assembly

4 вправи

Про концепцію Умовні конструкції

Прапорці rflags

Існує спеціальний регістр під назвою rflags. Його біти поводяться як прапорці для різних умов.

Деякі з них наведено нижче:

назва символ біт
перенесення CF 0
нуль ZF 6
знак SF 7
переповнення OF 11

Повний перелік шукайте в посібнику Intel.

Інструкції порівняння

Прапорці в rflags не змінюються напряму. Натомість їх встановлюють багато різних інструкцій.

Наприклад, ZF встановлюють багато арифметичних або порозрядних операцій, коли результат дорівнює нулю.

Одна з найпоширеніших інструкцій для перевірки умов - cmp. Вона приймає два операнди й оновлює прапорці, але не змінює своїх операндів.

Інструкція cmp

Інструкція cmp віднімає другий операнд від першого і встановлює прапорці відповідно до результату.

Якщо A - перший операнд, а B - другий:

прапорець встановлюється, коли
CF A < B (беззнакове)
ZF A == B
SF A < B (знакове, без переповнення)
OF переповнення під час знакового віднімання

Розгалуження

За замовчуванням код у x86-64 виконується послідовно, згори вниз.

Але є багато ситуацій, коли цю поведінку потрібно змінити. Наприклад, щоб виконати інший набір інструкцій у відповідь на умову.

У мовах вищого рівня це зазвичай роблять за допомогою абстракцій, як-от умовні конструкції if...else. Однак у x86-64 їх немає.

Натомість x86-64 надає інструкції, які фактично передають виконання в інше місце коду. Це називається branching.

Note

Ми вже бачили дві такі інструкції: call і ret.

Коли викликають функцію, виконання передається від функції, що викликає, до викликаної функції. А під час повернення виконання передається назад до функції, що викликала.

Якщо ret не знайдено, виконання «провалюється» з однієї функції в наступну. Іноді це можна використати, щоб оптимізувати потік виконання коду.

Безумовний перехід

Інструкція jmp безумовно передає виконання програми в іншу точку коду. Її єдиний операнд - це мітка, яка містить адресу точки, з якої виконання продовжиться.

Розгляньмо, наприклад, таку функцію:

fn:
    mov rax, 5
    jmp end

    add rax, 10
end:
    ret

Коли викликають fn, виконання починається з mov rax, 5. На цьому етапі значення в rax стає рівним 5.

Наступна інструкція - jmp end, яка передає виконання на мітку end.

Після end наступна інструкція - ret, яка передає виконання назад до функції, що викликала.

Зауважмо, що оскільки add rax, 10 розташована після jmp end і перед end, вона ніколи не виконується. Значення rax на момент повернення з fn дорівнює 5.

Умовний перехід

Сімейство інструкцій jcc передає виконання програми в іншу точку лише за виконання певної умови. Інакше виконання продовжується послідовно.

Кожна умова відповідає одному або кільком прапорцям у rflags. Деякі варіанти jcc перевіряють, що прапорець встановлено, інші - що його скинуто.

cc у jcc - не буквальне позначення, а вказівка на конкретний суфікс, повʼязаний із прапорцем, який перевіряють.

Суфіксів багато, і чимало з них перевіряють ту саму умову, що й інший суфікс. Деякі з них посилаються безпосередньо на прапорець, тож інструкція переходить на мітку, якщо цей прапорець встановлено:

суфікс переходить, якщо
z ZF == 1
c CF == 1
s SF == 1
o OF == 1

Багато інших обрано так, щоб відображати їхнє значення в інструкції cmp. Наприклад:

інструкція суфікс переходить, якщо
cmp A, B e A == B
cmp A, B l A < B (знакове)
cmp A, B b A < B (беззнакове)
cmp A, B g A > B (знакове)
cmp A, B a A > B (беззнакове)

Можна додати e після l, b, g або a, щоб урахувати рівність в умові:

cmp rcx, r8
jge two      ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two      ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison

Для всіх суфіксів є варіанти, які перевіряють протилежну поведінку. Вони мають той самий синтаксис, але з n перед суфіксом.

Наприклад, jnz переходить, коли ZF не встановлено. Так само jnae переходить, коли A не >= B (A і B розглядаються як беззнакові цілі числа).

Note

Деякі суфікси є синонімами для тих самих умов. Наприклад, jz (суфікс z, для ZF) і je (суфікс e, для рівності) обидва переходять, коли ZF встановлено. Це тому, що в cmp ZF встановлюється, коли віднімання дає нуль, а це відповідає рівності двох операндів.

Однак інші суфікси перевіряють комбінацію прапорців, і їх не можна безпосередньо замінити суфіксом одного прапорця.

Краще обирати той суфікс, який точніше описує семантику порівняння.

Локальні мітки

Мітки видно в усьому вихідному файлі, вони не є локальними для функції. Тому повторно використати назву мітки неможливо.

Щоб відтворити поведінку локальної мітки, NASM має спеціальний синтаксис для мітки, оголошеної з крапкою (.) перед нею. Цей синтаксис задає мітку, яка неявно містить назву попередньої мітки без крапки:

section .text
fn1:
    ...
.example: ; this is fn1.example
    ...
    ret

fn2:
    ...
.example: ; this is fn2.example
    ...
    ret

Усе ще можна перейти на цю мітку з будь-якого місця коду, використавши повну назву мітки, наприклад jmp fn1.example.

Однак перехід, який використовує частину назви мітки, починаючи з крапки, відбудеться на мітку всередині зовнішньої функції. Наприклад, .example поводиться так, ніби є локальною для функції:

section .text
fn1:
    ...
.example:
    ...
    jmp .example ; this jumps to fn1.example

fn2:
    ...
.example:
    ...
    jmp .example ; this jumps to fn2.example

Зауважмо, що мітка без крапки всередині функції на практиці задає ще одну функцію:

section .text
fn1:
    ...
.example: ; this is 'fn1.example'
    ...
non_dotted:
    ...
.example: ; this is 'non_dotted.example'
    ...
    ret
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Умовні конструкції