שילוב אסמבלי ושפת C: נכתב ע"י Crossbow.

שלום גבירותיי ורבותיי. מדריך זה נכתב לכותבי מערכות הפעלה למיניהן,
לאנשים שרוצים שהתוכניות שלהן יהיו מהירות יותר, ולאנשים שממש בא ללמוד
משהו חדש. מדריך זה דורש ידע בינוני בשפת אסמבלי וכמו כן ידע בשפת C.
כאשר המהירות וגודל התוכנית הן גורמים קריטיים, נהוג לשלב בין שפה עילית לשפת הסף - אסמבלי.

שילוב פקודות
על מנת לשלב פקודות אסמבלי בתוכנית בשפת C, יש להשתמש במילה השמורה
המיוחדת למהדר (Compiler) הספציפי שלנו. ברוב המהדרים המילה היא:

_ _asm

לאחר המילה השמורה אפשר להמשיך את השורה בכתיבת פקודת אסמבלי, כמו:

_ _asm mov al,7h;

ישנם מתכנתים שלא מסיימים בסימן הנקודה פסיק (;) בסוף פקודת אסמבלי משולבת. אני ממליץ דווקא כן.
כתיבת הערות היא כמו בשפת C רגילה. לדוגמא, להלן הערה:

_ _asm pop ax; // Moves the stack's last value into ax...

ניתן גם לכתוב מספר פקודות אסמבלי, על ידי פתיחת בלוק, כגון:

_ _asm {
   mov al,7h;
   inc al;
   xor al,01101101b;
}

הערה חשובה: חשוב ביותר שפתיחת הבלוק על ידי הסוגריים המסולסלים תהיה אך ורק בשורה שבה כתבנו asm. אם לא, המהדר עלול לטעות ולפרש זאת כבלוק של שפת C ולא של אסמבלי.

שילוב משתנים
עד עכשיו רק הכנסנו פקודות רגילות של אסמבלי בתוך שפת C. מה ייקרה אם אנו נרצה לשלב משתנים משפת C בתוך חלק האסמבלי? לשם כך נלמד כעת על שילוב משתנים. שילוב המשתנים הוא פשוט ביותר. לדוגמא, נכתוב תוכנית בשפת C עם משתנה a המוגדל בשבע בתוך קטע האסמבלי:

#include <stdio.h>
void main(void)
{
   int a=3;
   _ _asm add a,7;
   printf("%d\n",a);
}

כמו כן, ניתן גם להכליל אסמבלי בפרוצדורות באותה צורה. לדוגמא:

int proc(int a, int b)
   _ _asm {
      mov over,0;
      add a,b;       jno procend;
      mov over,1;
      procend: nop;
   }
   return(a);
}

פרוצדורה זו משתמשת ביתרונות של שפת אסמבלי ומשלבת אותם עם C. פרוצדורה
זו תפעל רק במהדרים חדישים יותר, מכיוון שיש כאן שימוש בתוויות (label)
ובקפיצות (jumps). הפרוצדורה מחברת את שני הארגומנטים, a, b, ובודקת אם
הייתה גלישה. אם הייתה, המשתנה over יקבל ערך 1. ישנם כמה חוקים חשובים
כאשר אנו משתמשים באסמבלי בתוך תוכנית C:

1. אם שינינו את ערכי האוגרים, יש להחזיר אותם לערכם לפני השינוי. את האוגרים הכלליים (ax,bx,cx,dx, החלקים העליוניים והתחתוניים שלהם) עדיף לא לשנות, אלא אם כן אנו מסיימים את הפרוצדורה ללא כל חלק אחר בשפת סי (חוץ מהמילה return).
2. אסור לשנות אף פעם נתונים במחסנית. כאשר אנו בונים פרוצדורה, המהדר דוחף (push) את ערכי האוגרים לתוך המחסנית, קורא לפרוצדורה, ולאחר מכן חוזר על ידי הקפצת ערך המחסנית לאוגרים (pop).
3. ישנם מהדרים ישנים (כגון Turbo C 3 לדוגמא), שלא תומכים באפשרויות שונות של אסמבלי, כגון קפיצות מותנות ושימוש בתוויות.

קישור אסמבלי עם שפת C
מהדר שפת C יוצר קובץ אובייקט (Object, עם סיומת obj). המקשר (Linker), מסוגל לקשר מספר קבצי Object לתוך קובץ הרצה יחיד (Executible). היתרון הוא, שאפשר לקשר קבצי אובייקט שונים, שנכתבו בשפות שונות, מכיוון שפורמט האובייקט (obj) הוא זהה למערכת ההפעלה הספציפית שלנו. למעשה, הספריות שאנו מכלילים בשפת C הם קבצים עם סיומת lib (ספרייה), המכילות פקודות מועילות. הקבצים הללו גם הם כתובים בפורמט obj. לדוגמא, נציג קישור של פרוצדורה בשפת אסמבלי עם שפת C. להלן קוד המקור של שפת אסמבלי:

CODE SEGMENT:
   Reset PROC NEAR
      int 19h;
      ret;
   Reset ENDP

פרוצדורה קרובה זו בשפת אסמבלי (תחביר MASM) קוראת לפסיקה המאתחלת את המחשב (Reset). כעת, נגדיר את הפרוצדורה בשפת C:

#include <stdio.h>
void Reset(void);
void main(void)
{
   Reset();
   exit(0);
}

שימו לב, בפעם הראשונה שכתבנו את שם הפרוצדורה (בשורה השנייה), אנו מכריזים על הפרוצדורה (בדומה לטקסט המוצג בתוך קבצי h). בפעם השנייה קראנו לפרוצדורה מתוך פונקציית main. לאחר הידור קובץ האסמבלי (נקרא לו Reset.asm) וקובץ שפת C (נקרא לו Sample.c), נקבל שני קבצי אובייקט. כדי לקשר אותם בעזרת מקשר ושמו link (בדרך כלל משומש עם MASM), נריץ:

link Sample.obj + Reset.obj

התוצאה תהיה הווצרות של תוכנית הרצה בשם Sample.exe. מכאן ניתן להסיק שהמהדר הוא שמחפש הגדרות נכונות לפונקציות ולפרוצדורות, בעוד המקשר מחבר את קבצי האובייקט, בודק תקינות ומקצה כתובות לכל פונקצייה, פרוצדורה ומשתנה.

העברת פרמטרים
כאשר אנו פונים משפת אסמבלי לשפת C, נדאג להוסיף קו תחתון (_) לכל שם בשפת C. לדוגמא, בשפת C יש לנו את הפרוצדורה putch, המקבלת ערך ומדפיסה את התו על המסך. יש להסתכל בתיעוד של הפרוצדורה. להלן הגדרתה בשפת C:

putch(int ch)

מה שעקרוני בשבילנו הוא שם המשתנה ch. עבור פרוצדורה זו יש ביצוע בכמה שלבים:

1. דחיפת ערך ch לתוך המחסנית על ידי push.
2. ביצוע קפיצה על ידי פקודת call.
3. הפרוצדורה משתמשת בנתון אך משאירה אותו במחסנית, באמצעות bp.
4. הכתובת נשלפת מתוך המחסנית על ידי ביצוע ret.
5. ניקוי המחסנית מהנתונים שהועברו על ידיה.

להלן קטע אסמבלי הקורא לפרוצדורה putch:

CODE SEGMENT:
   _PutchProc PROC NEAR
      mov ax,ch;
      push ax;
      call _putch;
      pop ax;
      ret;
   _PutchProc ENDP

למעשה, העברנו כאן את ערכו של הארגומנט ch דרך המחסנית. על מנת לקבל ערכים מוחזרים, נשתמש בגודל יעד. לדוגמא, כדי להחזיר בית (char לדוגמא), נשתמש בחצי אוגר (al). כדי להחזיר מילה (int לדוגמא), נשתמש באוגר שלם (ax). כדי להחזיר מילה כפולה נשתמש בשני אוגרים (ax, dx, כאשר ax הוא המילה התחתונה ו- dx מילה עליונה).

הערות חשובות לסיום
1. מהדר של שפת C מגדיר קריאה לפונצייה בשם main (עם קו תחתון לפניה) באופן אוטומאטי (פונקציית main שמורה על ידי מהדרי C).
2. כאשר אנו מעבירים מספר פרמטרים לפרוצדורה, הם נדחפים למחסנית בסדר הפוך מהופעתם. לכן, קריאת הערכים הללו על ידי pop תציג לנו אותם בסדר הנכון (עקרון LIFO - Last In First Out המיושם במחסנית).
3. לאחר הקריאה, הפרמטרים משוחררים מהמחסנית, כלומר, אם יש לנו שלושה פרמטרים בפרוצדורה, המעבד יבצע push שלוש פעמים בקריאה לפונקצייה, ויבצע pop שלוש פעמים עם הפקודה ret. לכן, אם השתמשנו במחסנית במהלך ביצוע הפרוצדורה, יש לוודא שמספר הנתונים במחסנית לא השתנה, כלומר, לבצע push או pop מספר פעמים.
4. מומלץ לדחוף למחסנית את ערך אוגר bp ולהשתמש בו לקריאת נתונים, אך יש צורך לשחזר אותו בסוף.
5. אם התבצעה קריאה לפסיקה, יש לכתוב את הפקודה cld מיד לפני החזרה. פקודה זו מאפסת את דגל הכיוון.
6. אם מעבירים מצביעים (pointers) בין אסמבלי לשפת C או להפך, יש לציין באופן מפורש אם המצביע הוא מסוג קרוב (NEAR) או רחוק (FAR).

מילים לסיום
אוקיי, אני מקווה שנהנתם מהמאמר הקצר הזה. אני ממליץ בחום להשתמש הרבה בתוכנות debug ולקרוא את ערכם של האוגרים, כדי להבין טוב מה קורה באופן תיאורטי לתוכנית. שלכם, קרוסבוו.