هناك سجل خاص يُسمّى rflags.
تعمل بتّاته كأعلام لحالات مختلفة.
وفيما يلي بعضها:
| الاسم | الرمز | البت |
|---|---|---|
| الحمل | CF | 0 |
| الصفر | ZF | 6 |
| الإشارة | SF | 7 |
| الفائض | OF | 11 |
للاطلاع على القائمة الكاملة، راجع دليل Intel.
لا تُعدَّل الأعلام في rflags مباشرةً.
بدلًا من ذلك، تضبطها تعليمات مختلفة كثيرة.
على سبيل المثال، يُضبط ZF في كثير من العمليات الحسابية أو عمليات البتّات عندما تكون النتيجة صفرًا.
ومن أكثر التعليمات شيوعًا في اختبار الشروط تعليمة cmp.
فهي تأخذ معاملين وتُحدّث الأعلام، لكنها لا تعدّل معامليها.
تطرح تعليمة cmp المعامل الثاني من الأول، وتضبط الأعلام وفقًا للنتيجة.
إذا كان A المعامل الأول وB المعامل الثاني:
| العلم | يُضبط عندما |
|---|---|
| CF | A < B (بدون إشارة) |
| ZF | A == B |
| SF | A < B (بإشارة، بدون فائض) |
| OF | فائض في الطرح بإشارة |
افتراضيًا، يُنفَّذ الكود في x86-64 بالتتابع من الأعلى إلى الأسفل.
لكن هناك حالات كثيرة يلزم فيها تعديل هذا السلوك. مثلًا، لتنفيذ مجموعة مختلفة من التعليمات استجابةً لشرط.
في اللغات عالية المستوى، يُنجَز هذا عادةً بتجريدات مثل الجمل الشرطية if...else.
لكن هذه لا وجود لها في x86-64.
بدلًا من ذلك، توفر x86-64 تعليمات تنقل التنفيذ فعليًا إلى موضع آخر في الكود.
وهذا ما يُسمّى branching.
لقد رأينا بالفعل تعليمتين من هذا القبيل: 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 كعددين صحيحين بدون إشارة).
بعض اللاحقات أسماء بديلة للشروط نفسها.
على سبيل المثال، jz (اللاحقة z، إشارةً إلى ZF) و je (اللاحقة e، إشارةً إلى التساوي) كلتاهما تقفز عندما يكون ZF مضبوطًا.
وذلك لأن ZF يُضبط مع cmp عندما يكون ناتج الطرح صفرًا، وهو ما يقابل تساوي المعاملين.
أما اللاحقات الأخرى فتختبر توليفة من الأعلام ولا يمكن استبدالها مباشرةً بلاحقة علم واحد.
فضّل اللاحقة التي تصف دلالة مقارنتك وصفًا أفضل.
اللافتات مرئية في ملف المصدر بأكمله، وليست محلية داخل دالة. لذا يستحيل إعادة استخدام اسم لافتة.
لمحاكاة سلوك اللافتة المحلية، لدى 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