الغوص في الأنظمة › 6. تحت C: الغوص في لغة التجميع 7 / 18 السابق التالي

6. تحت السطح: الغوص في لغة التجميع

تحت السطح، تحت السطح

ألا تعلم أنه خير

من التعامل مع السجلات

ولغة التجميع؟

-سيباستيان، على الأرجح

قبل اختراع المترجم (compiler) في الأيام الأولى للحوسبة، كان كثير من المبرمجين يكتبون برامجهم بلغة التجميع (assembly language)، التي تحدّد مباشرة مجموعة التعليمات التي يتبعها الحاسوب أثناء التنفيذ. ولغة التجميع هي أقرب ما يصل إليه المبرمج إلى البرمجة على مستوى الآلة من دون كتابة الشيفرة مباشرة بالآحاد والأصفار، وهي صورة مقروءة من شيفرة الآلة (machine code). ولكتابة شيفرة تجميع فعّالة، يجب على المبرمجين أن يفهموا فهمًا دقيقًا طريقة عمل معمارية الآلة الأساسية.

غيّر اختراع المترجم جذريًا الطريقة التي يكتب بها المبرمجون الشيفرة. فـالمترجم (compiler) يترجم لغة برمجة مقروءة بشريًا (تُكتب عادةً باستخدام كلمات إنجليزية) إلى لغة يفهمها الحاسوب (أي شيفرة الآلة). ويترجم المترجمون الشيفرة المقروءة بشريًا إلى شيفرة آلة بالاستناد إلى قواعد لغة البرمجة ومواصفات نظام التشغيل ومجموعة تعليمات الآلة، ويوفّرون قدرًا من اكتشاف الأخطاء وفحص الأنواع أثناء ذلك. وتنتج معظم المترجمات الحديثة شيفرة تجميع لا تقل كفاءة عن شيفرة التجميع المكتوبة يدويًا في الأزمنة الغابرة.

فوائد تعلّم لغة التجميع

بالنظر إلى كل فوائد المترجمات، قد لا يكون واضحًا سبب فائدة تعلّم لغة التجميع. غير أن هناك عدة أسباب مقنعة لتعلّم شيفرة التجميع وفهمها. إليك بعض الأمثلة.

1. التجريد عالي المستوى يخفي تفاصيل قيّمة عن البرنامج

يُعدّ التجريد الذي توفّره لغات البرمجة عالية المستوى نعمة لتقليل تعقيد البرمجة. وفي الوقت نفسه، يسهّل هذا التبسيط على المبرمجين اتخاذ قرارات تصميمية من دون فهم كامل لتبعات خياراتهم على مستوى الآلة. وغالبًا ما يمنع الجهل بلغة التجميع المبرمجَ من فهم معلومات قيّمة حول طريقة عمل البرنامج، ويحدّ من قدرته على فهم ما تفعله شيفرته فعليًا.

على سبيل المثال، ألقِ نظرة على البرنامج التالي:

#include <stdio.h>

int adder() {
    int a;
    return a + 2;
}

int assign() {
    int y = 40;
    return y;
}

int main(void) {
    int x;
    assign();
    x = adder();
    printf("x is: %d\n", x);
    return 0;
}

ما مخرجات البرنامج؟ للوهلة الأولى، تبدو الدالة assign بلا أثر، إذ لا يخزّن أي متغير في main قيمة إرجاعها. وتعيد الدالة adder قيمة a + 2، مع أن المتغير a غير مهيأ (وإن كان المترجم ستهيئه إلى 0 على بعض الأجهزة). ومن المتوقع أن تؤدي طباعة x إلى قيمة غير معرّفة. غير أن ترجمة البرنامج وتشغيله على معظم الأجهزة ذات المعالجات 64-بت ينتج باستمرار الإجابة 42:

$ gcc -o example example.c
$ ./example
x is: 42

تبدو مخرجات هذا البرنامج غير منطقية للوهلة الأولى، إذ يبدو أن الدالتين adder وassign غير مترابطتين. وسيساعدك فهم إطارات المكدّس (stack frames) وكيفية تنفيذ الدوال في العمق على إدراك سبب أن الإجابة هي 42. وسنعود إلى هذا المثال في الفصول القادمة.

2. بعض الأنظمة الحاسوبية محدودة الموارد أكثر من أن تناسب المترجمات

أكثر أنواع "الحواسيب" شيوعًا هي تلك التي لا نتبيّن بسهولة أنها حواسيب. فهذه الأجهزة موجودة في كل مكان، من السيارات وآلات صنع القهوة إلى الغسالات والساعات الذكية. وتؤدي المستشعرات (sensors) والمتحكمات الدقيقة (microcontrollers) وغيرها من المعالجات المدمجة دورًا متزايد الهيمنة في حياتنا، وكلها تحتاج إلى برمجيات لتعمل. غير أن المعالجات الموجودة في مثل هذه الأجهزة غالبًا ما تكون صغيرة جدًا إلى حد لا يمكّنها من تنفيذ الشيفرة المترجمة المكتوبة بلغات برمجة عالية المستوى. وفي كثير من الحالات، تحتاج هذه الأجهزة إلى برامج تجميع مستقلة لا تعتمد على مكتبات التشغيل التي تتطلبها لغات البرمجة الشائعة.

3. تحليل الثغرات

تقضي فئة من المتخصصين في الأمن السيبراني أيامهم في محاولة اكتشاف الثغرات (vulnerabilities) في مختلف أنواع الأنظمة الحاسوبية. وتتعلق سبل كثيرة لاستغلال البرامج بالطريقة التي يخزّن بها البرنامج معلوماته أثناء التنفيذ. ويمكّن تعلّم لغة التجميع هؤلاء المتخصصين من فهم كيف تنشأ الثغرات وكيف يمكن استغلالها.

ويقضي متخصصون آخرون في الأمن وقتهم في "الهندسة العكسية" (reverse engineering) للشيفرة الخبيثة في البرمجيات الضارة وغيرها. والمعرفة العملية بلغة التجميع ضرورية لتمكين هؤلاء المهندسين من تطوير تدابير مضادة بسرعة لحماية الأنظمة من الهجمات. وأخيرًا، قد ينتهي المطاف بالمطورين الذين يفتقرون إلى فهم كيفية ترجمة شيفرتهم إلى لغة التجميع إلى كتابة شيفرة قابلة للاستغلال من دون قصد.

4. تسلسلات الشيفرة الحرجة في برمجيات مستوى النظام

وأخيرًا، توجد بعض مكونات النظام الحاسوبي التي لا تستطيع المترجمات تحسينها تحسينًا كافيًا، وتتطلب لغة تجميع مكتوبة يدويًا. فبعض مستويات النظام تحتوي شيفرة تجميع مكتوبة يدويًا في مجالات تكون فيها التحسينات الدقيقة الخاصة بالآلة حاسمة للأداء. فعلى سبيل المثال، تُكتب تسلسلات الإقلاع (boot sequence) في جميع الحواسيب بلغة التجميع. وتحتوي أنظمة التشغيل غالبًا على شيفرة تجميع مكتوبة يدويًا لتبديل سياق الخيوط أو العمليات. وكثيرًا ما يستطيع البشر إنتاج شيفرة تجميع أفضل تحسينًا من المترجمات في هذه التسلسلات القصيرة الحاسمة للأداء.

ما ستتعلمه في الفصول القادمة

تغطي الفصول الثلاثة التالية ثلاث صور مختلفة من لغة التجميع. يغطي الفصل 7 والفصل 8 معماريتي x86_64 وصورتها الأقدم IA32. ويغطي الفصل 9 لغة تجميع ARMv8-A، وهي بنية مجموعة التعليمات (ISA) الموجودة في معظم أجهزة ARM الحديثة، بما فيها الحواسيب أحادية اللوحة مثل Raspberry Pi. ويحتوي الفصل 10 على ملخص وبعض الخلاصات الأساسية لتعلّم لغة التجميع.

وكل صورة من صور لغة التجميع هذه تنفّذ بنية مجموعة تعليمات (instruction set architecture, ISA) مختلفة. وتذكّر أن بنية مجموعة التعليمات تحدّد مجموعة التعليمات وترميزها الثنائي، ومجموعة سجلات وحدة المعالجة المركزية (CPU registers)، وآثار تنفيذ التعليمات على حالة المعالج والذاكرة.

وسترى في الفصول الثلاثة التالية أوجه شبه عامة بين جميع بنى مجموعات التعليمات، منها أن سجلات وحدة المعالجة المركزية تُستخدم معاملات (operands) لكثير من التعليمات، وأن كل بنية توفّر أنواعًا متشابهة من التعليمات:

  1. تعليمات لحساب العمليات الحسابية والمنطقية، مثل الجمع أو عملية AND على مستوى البتات
  2. تعليمات للتحكم في مسار التنفيذ تُستخدم لتنفيذ التفريعات مثل if-else والحلقات واستدعاء الدوال والعودة منها
  3. تعليمات لنقل البيانات تُحمّل القيم وتخزّنها بين سجلات وحدة المعالجة المركزية والذاكرة
  4. تعليمات لدفع القيم إلى المكدّس (stack) وسحبها منه. تُستخدم هذه التعليمات لتنفيذ مكدّس الاستدعاء، حيث يُضاف إطار جديد من ذاكرة المكدّس (يخزّن المتغيرات المحلية والمعاملات لدالة قيد التنفيذ) إلى قمة المكدّس عند استدعاء دالة، ويُزال إطار من قمة المكدّس عند العودة من دالة.

يترجم مترجم لغة C الشيفرة المصدرية إلى مجموعة تعليمات بنية ISA محددة. ويترجم المترجم عبارات C، بما فيها الحلقات وif-else واستدعاءات الدوال والوصول إلى المتغيرات، إلى مجموعة محددة من التعليمات تعرّفها بنية ISA وتنفّذها وحدة معالجة مصممة لتنفيذ تعليمات هذه البنية. فمثلًا، يترجم المترجم C إلى تعليمات x86 لتُنفَّذ على معالج Intel x86، أو يترجم C إلى تعليمات ARM لتُنفَّذ على معالج ARM.

أثناء قراءتك فصول قسم لغة التجميع في الكتاب، قد تلاحظ إعادة تعريف بعض المصطلحات الأساسية وإعادة إنتاج بعض الأشكال. وقد صمّمنا كل فصل ليُستخدم باستقلالية في كليات وجامعات معينة، إسهامًا منا في خدمة معلمي علوم الحاسوب الآخرين على أفضل وجه. ورغم أن معظم المادة في كل فصل فريدة، نأمل أن تساعد أوجه الشبه بين الفصول على ترسيخ التشابهات بين صور لغة التجميع المختلفة في أذهان القراء.

هل أنت مستعد لتعلّم لغة التجميع؟ لنغُص مباشرة إذن! اتبع الروابط أدناه لزيارة الفصول التي تهمك: