يمكن استخدام كل بت في عدد صحيح لتخزين قيمة ثنائية. ولأن كثيرًا من الحالات تتضمن معلومات ثنائية، مثل صحيح أو خطأ، أو التضمين أو الاستبعاد، أو التشغيل أو الإطفاء، فإن التمثيل الثنائي لعدد صحيح من N بت يوفّر طريقة مدمجة لترميز الحالة الثنائية لعدد N من العناصر. وهذا يجعل القدرة على التلاعب بالبتات والبايْتات أمرًا أساسيًا في لغة التجميع. توفّر مجموعة تعليمات x86-64 تنويعًا واسعًا من تعليمات التلاعب بالبتات.
تعمل هذه التعليمات على بتات مفردة داخل طرف واحد.
وتأخذ جميعها طرفين، حيث يشير الطرف الثاني إلى فهرس البت الذي يتم العمل عليه في الطرف الأول. وتنسخ جميعها البت المحدد إلى علم الحمل (CF).
| Name | Description |
|---|---|
bt |
ينسخ البت إلى CF دون تعديل أي طرف |
bts |
ينسخ البت إلى CF ويضبطه في طرف الوجهة |
btr |
ينسخ البت إلى CF ويصفّره في طرف الوجهة |
btc |
ينسخ البت إلى CF ويكمّله (يقلبه) في طرف الوجهة |
تُنفَّذ العمليات على مستوى البتات على جميع بتات الطرف.
ولكل منها تعليمة تحمل الاسم نفسه للعملية التي تُنفَّذ على مستوى البتات:
| Name | Description |
|---|---|
and |
القيمة 1 إذا كان البتان كلاهما 1 |
or |
القيمة 1 إذا كان أحد البتين على الأقل 1 |
xor |
القيمة 1 إذا اختلف البتان |
not |
القيمة 1 إذا كان البت 0؛ و0 إذا كان البت 1 |
تأخذ معظمها طرفين، وتُجري عملية على مستوى البتات على كليهما، وتخزّن النتيجة في طرف الوجهة.
والاستثناء هو not، الذي يأخذ طرف وجهة واحدًا فقط.
عندما نفسّر الواحد والصفر على أنهما تضمين واستبعاد على الترتيب، يُسمّى العدد الصحيح قناع بتات (أو ببساطة قناع).
يقوم قناع البتات بـ"حجب" العناصر لأن الصفر في البت رقم i يستبعد العنصر رقم i، بينما الواحد يضمّه.
ونستخدم أيضًا قناع البتات بشكل شائع لتضمين بتات معينة من عدد صحيح مع استبعاد غيرها.
على سبيل المثال، لتكن A عددًا صحيحًا تمثيله الثنائي هو:
| index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| bits | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
ولتكن M أيضًا عددًا صحيحًا تمثيله الثنائي هو:
| index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| bits | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
كلاهما عددان صحيحان من 8 بت.
في هذه الحالة، يمكننا القول إن M يختار البتات 0 و2 و3 من A، ويستبعد الباقي.
تفيد تعليمات العمل على مستوى البتات التي ناقشناها سابقًا في التلاعب بالأعداد الصحيحة باستخدام الأقنعة. على سبيل المثال:
A التي لا يختارها M، استخدم عملية AND على مستوى البتات: A AND M.A التي يختارها M، استخدم عملية OR على مستوى البتات: A OR M.test
تُجري التعليمة test عملية AND على مستوى البتات بين الطرفين، وتضبط الأعلام وفقًا للنتيجة.
إذا كان A هو الطرف الأول وB الطرف الثاني:
| flag | set when |
|---|---|
CF |
يُصفَّر دائمًا |
ZF |
A AND B == 0 |
SF |
يُضبط بت الإشارة لـ A AND B |
OF |
يُصفَّر دائمًا |
تأخذ هذه التعليمة طرفين وتحدّث الأعلام، لكنها لا تعدّل طرفيها.
تنقل هذه التعليمات البتات في طرف الوجهة بعدد من المواضع يحدّده الطرف الثاني.
ويجب أن يكون الطرف الثاني عددًا ثابتًا (immediate) أو السجل cl (أدنى 8 بتات من rcx).
| Name | Description |
|---|---|
shl/sal
|
إزاحة البتات إلى اليسار |
shr/sar
|
إزاحة البتات إلى اليمين |
لاحظ أن العدد في الطرف الثاني يُحجب إلى 5 بتات، أو 6 بتات مع طرف وجهة من 64 بت.
وأي بت بعد ذلك يُتجاهل فعليًا.
وهذا يعني أن أقصى إزاحة هي 31، أو 63 مع طرف من 64 بت.
shl / sal
تُجري كلٌّ من shl وsal العملية نفسها تمامًا، فواحدة منهما اسم بديل للأخرى.
كلما أُجريت إزاحة إلى اليسار، تُنقل البتات الأقرب إلى نهاية التسلسل بمقدار طول الإزاحة أولًا إلى CF ثم تُهمَل.
وفي المقابل، يُضاف إلى البداية عدد من البتات المصفّرة الجديدة يساوي طول الإزاحة.
ولأن كل بت في العدد الصحيح يمثّل قوة للعدد 2، فإن الإزاحة إلى اليسار بمقدار n موضعًا تؤدي إلى ضرب العدد الصحيح في 2ⁿ.
shr / sar
هناك تعليمتان لنقل البتات إلى اليمين: shr وsar.
عند استخدام أي من التعليمتين، تُنقل البتات الأقرب إلى بداية التسلسل بمقدار طول الإزاحة أولًا إلى CF ثم تُهمَل.
وفي المقابل، يُضاف إلى النهاية عدد من البتات الجديدة يساوي طول الإزاحة.
والفرق بينهما أن shr تنقل بتات 0 إلى الطرف الأيسر، بينما تنقل sar القيمة 1 إذا كان البت الأكثر أهمية مضبوطًا، و0 خلاف ذلك.
وهذا يعني أن sar تحافظ على الإشارة عند إزاحة عدد صحيح موقّع.
ولأن كل بت في العدد الصحيح يمثّل قوة للعدد 2، فإن الإزاحة إلى اليمين بمقدار n موضعًا باستخدام shr تؤدي إلى قسمة غير موقّعة على 2ⁿ.
وبالمثل، فإن الإزاحة إلى اليمين بمقدار n موضعًا باستخدام sar تؤدي إلى قسمة موقّعة على 2ⁿ.
تنقل هذه التعليمات البتات في طرف الوجهة بعدد من المواضع يحدّده الطرف الثاني.
ويجب أن يكون الطرف الثاني عددًا ثابتًا (immediate) أو السجل cl (أدنى 8 بتات من rcx).
الفرق بين التدوير والإزاحة أن التدوير لا يهمل أي بتات ولا يضيف أيًا منها. فالبتات التي كانت ستُهمَل في الإزاحة تُنقل بدلًا من ذلك إلى الطرف المقابل. وهكذا تبقى جميع البتات، لكنها كلها تغيّر مواضعها.
| Name | Description |
|---|---|
rol |
تدوير البتات إلى اليسار |
ror |
تدوير البتات إلى اليمين |
لاحظ أن العدد في الطرف الثاني يُحجب إلى 5 بتات، أو 6 بتات مع طرف وجهة من 64 بت.
وأي بت بعد ذلك يُتجاهل فعليًا.
وهذا يعني أن أقصى تدوير هو 31، أو 63 مع طرف من 64 بت.
هناك تعليمات أخرى مفيدة للتلاعب بالبتات:
| Name | Description |
|---|---|
popcnt |
يحصي عدد البتات المضبوطة |
bsr |
يحصل على فهرس أكثر بتات مضبوطة أهمية. وإذا لم يكن أي بت مضبوطًا، تكون النتيجة غير معرّفة |
bsf |
يحصل على فهرس أقل بتات مضبوطة أهمية. وإذا لم يكن أي بت مضبوطًا، تكون النتيجة غير معرّفة |
تعمل هذه التعليمات جميعها مع طرفين من 16 بت أو 32 بت أو 64 بت.
ولا يمكن استخدامها مع أطراف من 8 بت.
أرسل إليك صديقك للتو رسالة تحوي سرًّا مهمًّا. ولكي لا يسهّل على الآخرين قراءتها، شُفّرت الرسالة بإجراء سلسلة من التلاعبات بالبتات. وستحتاج إلى كتابة الطُرق للمساعدة في فك تشفير الرسالة.
هذه هي تعليمات البت الواحد المذكورة في هذا المفهوم:
| الاسم | الوصف |
|---|---|
| bt | ينسخ البت إلى CF دون تعديل أي معامل |
| bts | ينسخ البت إلى CF ويضبطه في معامل الوجهة |
| btr | ينسخ البت إلى CF ويصفّره في معامل الوجهة |
| btc | ينسخ البت إلى CF ويكمّله (يقلبه) في معامل الوجهة |
هذه هي التعليمات البتّية المذكورة في هذا المفهوم:
| الاسم | الوصف |
|---|---|
| and | 1 إذا كان كلا البتين يساوي 1 |
| or | 1 إذا كان أحد البتين على الأقل يساوي 1 |
| xor | 1 إذا اختلف البتان |
| not | 1 إذا كان البت 0؛ و0 إذا كان البت 1 |
هذه هي تعليمات الإزاحة المذكورة في هذا المفهوم:
| الاسم | الوصف |
|---|---|
| shl/sal | يزيح البتات إلى اليسار |
| shr/sar | يزيح البتات إلى اليمين |
هذه هي تعليمات التدوير المذكورة في هذا المفهوم:
| الاسم | الوصف |
|---|---|
| rol | يدوّر البتات إلى اليسار |
| ror | يدوّر البتات إلى اليمين |
هذه هي التعليمات المتنوعة المذكورة في هذا المفهوم:
| الاسم | الوصف |
|---|---|
| popcnt | يعدّ عدد البتات المضبوطة |
| bsr | يجلب فهرس أعلى بت مضبوط. إذا لم يُضبط أي بت، تكون النتيجة غير معرّفة |
| bsf | يجلب فهرس أدنى بت مضبوط. إذا لم يُضبط أي بت، تكون النتيجة غير معرّفة |
الرسالة مشفّرة داخل عدد صحيح من 16 بت. غير أن أعلى 8 بتات منها ليست في الحقيقة جزءًا من الرسالة، بل قناعًا يجب استخدامه في فك التشفير.
نفّذ الدالة extract_higher_bits التي تأخذ عددًا صحيحًا من 16 بت وتُرجع أعلى 8 بتات منه.
extract_higher_bits(0b1010010011000101)
// => 0b10100100
لا تكفي القدرة على استخراج القناع، بل عليك أيضًا عزل الرسالة.
نفّذ الدالة extract_lower_bits التي تأخذ عددًا صحيحًا من 16 بت وتُرجع أدنى 8 بتات منه.
extract_lower_bits(0b1010010011000101);
// => 0b11000101
هناك بتات مضبوطة في كلٍّ من الرسالة والقناع. وهذه معلومة مهمة جدًّا سيُستعان بها لاحقًا.
نفّذ الدالة extract_redundant_bits التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع عددًا صحيحًا من 8 بتات تكون فيه البتات الزائدة وحدها مضبوطة.
يجب ضبط البت في العدد المُرجَع على 1 حيث يكون أيضًا 1 في كلٍّ من الرسالة والقناع.
أما بقية البتات فيجب تصفيرها.
extract_redundant_bits(0b1010010011000101);
// => 0b10000100
بعد ذلك، هناك بعض البتات التي يجب ضبطها على 1 في الرسالة، وفقًا للقناع.
نفّذ الدالة set_message_bits التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع نتيجة ضبط بتات الرسالة على 1.
يجب ضبط البت في الرسالة على 1 حيث يكون البت في القناع 1.
أما بقية البتات فيجب إبقاؤها دون تغيير، بحيث تبقى مضبوطة إن كانت مضبوطة من قبل، ومصفّرة إن كانت مصفّرة من قبل.
set_message_bits(0b1010010011000101);
// => 0b11100101
هناك قطعة من الأحجية غير ظاهرة صراحةً في الرسالة: العدد 0b1011001100111100 المكوّن من 16 بت.
هذا العدد هو مفتاحك الخاص المشترك، وعليك استخدامه للمساعدة في فك تشفير الرسالة.
وللقيام بذلك، عليك أولًا تدوير بتات مفتاحك الخاص إلى اليسار بعدد معيّن من المواضع. وهذا العدد من المواضع يساوي عدد البتات الزائدة المضبوطة في كلٍّ من الرسالة والقناع.
نفّذ الدالة rotate_private_key التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع نتيجة تدوير مفتاحك الخاص.
هذه النتيجة عدد صحيح من 16 بت.
rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
يدعم NASM (The Netwide Assembler، وهو المجمّع المستخدم في هذا المسار) كتابة الثوابت بالصيغة الثنائية مع بادئة 0b.
كما يدعم استخدام الشرطة السفلية (_) فاصلًا داخل الثابت، لتحسين القراءة:
PRIVATE_KEY equ 0b1011_0011_0011_1100
لكي يُستخدم في فك التشفير، يجب تنسيق مفتاحك الخاص لعزل البتات ذات الصلة.
لتنسيق المفتاح الخاص تنسيقًا كاملًا، عليك:
البت المقلوب يساوي 1 إذا كان 0، و0 إذا كان 1.
نفّذ الدالة format_private_key التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع مفتاحًا خاصًّا منسّقًا بالكامل من 8 بتات.
format_private_key(0b1010010011000101);
// => 0b11000001
بمجرد أن تصبح لديك الرسالة بجميع بتاتها ذات الصلة مضبوطة والمفتاح الخاص المنسّق، يحين وقت جمعهما معًا للحصول على الرسالة الناتجة.
الرسالة الناتجة عدد صحيح من 16 بت، يتكوّن مما يلي:
نفّذ الدالة decrypt_message التي تأخذ عددًا صحيحًا من 16 بت يرمّز كلا من الرسالة والقناع، وتُرجع عددًا صحيحًا من 16 بت مع فك تشفير الرسالة بالكامل.
ينبغي لهذه الدالة أن تستفيد من المفتاح الخاص المنسّق الذي تولّده عبر format_private_key، وكذلك من الرسالة بجميع بتاتها ذات الصلة المضبوطة عبر set_message_bits.
decrypt_message(0b1010010011000101);
// => 0b1100000111100101
سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.