لوحة الألوان

لوحة الألوان

تمرين تعلّمي

مقدمة

الذاكرة

عادةً ما يقوم نظام التشغيل (OS) بتخطيط الذاكرة لبرنامج ما وفق تخطيط عام:

العنوان منطقة الذاكرة
عالية المكدس
...
الكومة
قطاع القراءة والكتابة
قطاع الكود/القراءة فقط
منخفضة محجوز

تُنظَّم الذاكرة في قطاعات على هيئة أقسام، لكل منها أذونات مختلفة.

كانت جميع الدوال التي عرّفناها حتى الآن في section .text. يحتوي هذا القسم على بيانات قابلة للتنفيذ، وهي للقراءة فقط. وتُستخدم أقسام أخرى لتعريف متغيرات البيانات، وقد تكون للقراءة فقط أو للقراءة والكتابة، لكنها غير قابلة للتنفيذ.

القسم .data

تُعرَّف البيانات المُهيّأة في section .data.

في NASM (المُجمِّع Netwide، وهو المُجمِّع المستخدم في هذا المسار)، يكون للمتغير المُهيّأ اسم، وتوجيه يحدد حجم البيانات، وقائمة قيم يفصل بينها فاصلة. ويفصل بين كلٍّ من هذه العناصر مسافة، ويمكن اختياريًا أن تتبع التسمية علامة :.

أهم التوجيهات وأحجام البيانات المرتبطة بها هي:

التوجيه الحجم
db 1 بايت
dw 2 بايت
dd 4 بايت
dq 8 بايت

على سبيل المثال، يعرّف هذا متغيرًا بحجم بايت اسمه space وقيمته 10:

section .data
    space db 10

المتغيرات المُعرَّفة في section .data قابلة للتغيير، أي أنها للقراءة والكتابة. كما أن لها مدة تخزين ساكنة، أي أنها توجد طوال مدة تشغيل البرنامج.

القسم .rodata

يشبه section .rodata القسم section .data. يحتوي القسمان على بيانات مُهيّأة تُعرَّف بالطريقة نفسها، ولهما مدة التخزين نفسها.

والفرق الرئيسي بينهما أن البيانات في section .rodata غير قابلة للتغيير، أي أنها للقراءة فقط.

Note

الثوابت المُعرَّفة باستخدام equ تختلف عن تلك المُعرَّفة في section .rodata.

الثابت المُعرَّف باستخدام equ لا يشغل مساحة في الذاكرة، ويستبدله المُجمِّع بقيمته مباشرة. بل هو في الحقيقة بديل عن تلك القيمة.

في المقابل، تُخزَّن الثوابت المُعرَّفة في section .rodata فعليًا في الذاكرة، ولها عنوان.

الوصول إلى البيانات

التسميات والإشارة غير المباشرة

يجب أن يكون للبيانات المُعرَّفة اسم مرتبط بها. ويسمى هذا الاسم تسمية.

التسمية رمز يشفّر العنوان المحدد للبيانات في الذاكرة. العناوين في x86-64 قيم بطول 64 بت.

في NASM، لا تؤدي محاولة الوصول إلى البيانات مباشرةً عبر تسميتها إلى الحصول على الذاكرة المخصصة، بل إلى عنوانها:

section .data
    example dq 27 ; this declares a 8-byte variable initialized with 27

section .text
fn:
    mov rax, example ; this stores the address of the declared variable in rax, not its contents
    ...

للوصول إلى محتوى عنوان في الذاكرة، يجب فكّ إشارته. ويسمى هذا الإشارة غير المباشرة.

في NASM، يتم ذلك باستخدام []:

section .data
    example dq -27 ; this declares a 8-byte variable initialized with -27

section .text
fn:
    mov rax, [example] ; this dereferences example and access the value stored in memory (-27)
    ...

لكن هناك بعض الحالات التي قد يلتبس فيها حجم الذاكرة التي فُكّت إشارتها. وفي تلك الحالات، يجب استخدام بادئة تحدد هذا الحجم.

هذه أهم البادئات وأحجامها في برنامج نمطي على x86-64:

البادئة الحجم
byte 1 بايت
word 2 بايت
dword 4 بايت
qword 8 بايت

يمكن كتابة التحميل نفسه مع ذكر الحجم صراحةً:

    mov rax, qword [example] ; same dereference, size stated explicitly

من الممارسات الجيدة استخدام بادئة دائمًا عند فك إشارة الذاكرة.

الكتابة إلى الذاكرة

تتم الكتابة إلى الذاكرة بالطريقة نفسها، عبر فك إشارة عنوان:

section .data
    example1 db 10            ; example1 is a 1-byte memory location initialized with value 10
    example2 dq -456          ; example2 is a 8-byte memory location initialized with value -456
    example3 dd 54            ; example3 is a 4-byte memory location initialized with value 54

section .text
fn:
    mov byte [example1], 20   ; example1 now has value 20
    mov qword [example2], rdx ; example2 now has value equal to the contents in rdx
    mov dword [example3], eax ; example3 now has value equal to the contents in eax

لاحظ أنه يمكنك استخدام معطيات الذاكرة في معظم التعليمات دون تحميل محتواها في سجل أولًا. لكن لا يمكن عادةً استخدامها في معطى المصدر ومعطى الوجهة معًا، بل في أحدهما فقط:

section .data
    example4 dw 4
    example5 dq -8
    example6 dd 15

section .text
fn:
    add word [example4], 5     ; example4 is now a 2-byte memory location with the value 4 + 5 = 9
    imul rax, qword [example5] ; rax = rax * (-8)
    ; this is not possible -> sub dword [example6], dword [example6]
التعليمة lea

رغم أنه يمكن استخدام mov لتخزين عنوان متغير في سجل، فإن هناك تعليمة مخصصة لهذا الغرض تحديدًا: lea.

تستخدم هذه التعليمة معطى على صيغة موضع ذاكرة، لكنها لا تقرأ الذاكرة. بل تحسب تعبير العنوان الفعّال وتكتب النتيجة في معطى الوجهة:

lea rax, [example] ; this stores the address of 'example' in rax

من الأسلوب المألوف أكثر استخدام lea لحساب عناوين الذاكرة وتخزينها في السجلات.

العنونة النسبية

عند الوصول إلى مواضع الذاكرة، فإن السلوك الافتراضي في NASM هو توليد عناوين مطلقة، أي عناوين ذاكرة ثابتة.

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

ويسمى هذا عادةً العنونة النسبية إلى rip.

في NASM يمكنك طلب وصول نسبي إلى rip باستخدام العامل rel:

mov rax, qword [rel variable]

يمكن أيضًا جعل العنونة النسبية هي الافتراضية لملف مصدر ما باستخدام default rel في أوله.

تُجمَّع جميع التمارين في هذا المسار وتُربط كـ PIE، لذلك ينبغي استخدام rel لتوليد عناوين نسبية.

الظهور

التسميات (الدوال والبيانات) المُعرَّفة في أي قسم (مثل .text و.data و.rodata) تكون مرئية داخل ملف المصدر نفسه. وإذا عُرِّفت بـ global، تكون مرئية لملفات المصدر الأخرى أيضًا.

وبالعكس، تكون التسميات المُعرَّفة في ملفات مصدر أخرى مرئية لملف المصدر الحالي إذا عُرِّفت بـ extern. وفي هذه الحالة، لا يوجد أي إشارة إلى حجم البيانات في لغة التجميع، ويجب معرفة ذلك مسبقًا.

default rel

section .data

global number1 ; 'number1' is a variable visible to other source files
number1 db 200

extern number2 ; 'number2' is a variable visible to the current source file, but defined in another

section .text

extern sum ; sum is a function visible to the current source file, but defined in another

fn:
    mov dil, byte [number1]
    mov sil, byte [number2]
    call sum
    ...

التعليمات

صديقك خوسيه مدرّس في مدرسة محلية. راودته فكرة لبعض التجارب الممتعة التي تُظهر كيف يمكن دمج الألوان لإنتاج ألوان مختلفة.

وقد طلب مساعدتك في إجراء تلك التجارب.

Note

يُمثَّل اللون في هذا التمرين بعدد مقداره 32 بت (4 بايت)، يرمّز قيمة RGB الخاصة به.

تتكوّن قيمة RGB من 3 قنوات، هي الأحمر و_الأخضر_ و_الأزرق_، تشغل كل واحدة منها 8 بتات (1 بايت). تُخصَّص البايت الرابع عادةً لقناة ألفا، لكن قيمتها في هذا التمرين ستكون فارغة (0).

1. الحصول على قيمة RGB للون

قيم كل لون مخزّنة بالفعل في جدول مُعرَّف في ملف مصدر آخر. ويُعرَّف اللون بعنوان فريد في هذا الجدول.

عرّف دالة get_color_value تُرجع قيمة 32 بت للون. تأخذ هذه الدالة معاملًا هو عنوان صالح لهذا اللون في جدول الألوان.

get_color_value(black)
// => 0

تلميح - 32 بت تكافئ 4 بايت.

2. إضافة اللون الأساسي

لكي يمزج خوسيه ألوانًا مختلفة، سيحدّد أولًا لونًا أساسيًا ويغيّر فقط اللون الثانوي المدمج معه.

عرّف دالة add_base_color تحفظ قيمة 32 بت للون في المتغير base_color، حتى يمكن استخدامها لاحقًا. لا تُرجع هذه الدالة أي قيمة، وتأخذ معاملًا هو عنوان اللون في جدول الألوان.

المتغير base_color تعرّفه أنت، ويجب أن يكون متاحًا من ملفات مصدر أخرى.

لن يكون هناك أكثر من لون أساسي واحد في الوقت نفسه. وإذا أُضيف لون أساسي جديد، فسيُهمَل القديم.

افتراضيًا، عند بدء البرنامج، يجب تهيئة base_color بقيمة 32 بت للون الأبيض، وهي 0xFFFFFF00.

تلميح - يقبل NASM الأعداد المعرّفة بالنظام الست عشري باستخدام 0x في البداية، كما في 0xFFFFFF00.

3. تعريف ثوابت للألوان الأولية

يتوقع خوسيه إجراء تركيبات كثيرة باستخدام الألوان الأولية، لذا يريد فصلها لوصول سريع. ولأنه يستخدم RGB لتمثيل الألوان، فإن الألوان الأولية هي:

  • RED، بقيمة 0xFF000000.
  • GREEN، بقيمة 0x00FF0000.
  • BLUE، بقيمة 0x0000FF00.

عرّف ثابتًا واحدًا لكل لون من تلك الألوان. ويجب أن تكون هذه الثوابت متاحة من ملفات مصدر أخرى.

4. دمج الألوان

يجب دمج الألوان وفق combining_function معرّفة في ملف مصدر آخر. تأخذ هذه الدالة معاملين هما قيمتا 32 بت لكل من base_color واللون الثانوي المراد مزجه معه. وتُرجع قيمة 32 بت للون المدمج.

عرّف دالة make_color_combination تدمج لونين وتحفظ النتيجة في الذاكرة. لا تُرجع هذه الدالة أي قيمة، وتأخذ معاملين بهذا الترتيب:

  • العنوان الذي ينبغي تخزين قيمة 32 بت للون المدمج فيه.
  • عنوان اللون الثانوي في جدول الألوان، ليدمج مع اللون الأساسي.
Caution

لاحظ أن combining_function قد تُعدّل القيم في السجلات التي تستخدمها. تأكد من حفظ أي متغير تحتاجه في الذاكرة قبل استدعاء الدالة.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
x86-64 Assembly Exercism

مستعد لبدء لوحة الألوان؟

سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.