المسارات
/
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 مضبوطًا. وذلك لأن 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
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم الجمل الشرطية