8086 Assembler Tutorial for Beginners (Part 3)

 Variables

הדרכה בתכנות אסמבלר 8086 למתחילים (חלק 3)  

 משתנים

Variable is a memory location. For a programmer it is much easier to have some value be kept in a variable named "var1" then at the address 5A73:235B, especially when you have 10 or more variables.

Our compiler supports two types of variables: BYTE and WORD.

משתנה הוא מיקום של זיכרון מוגדר על ידי שם.
למתכנת הרבה יותר קל כאשר יש ערך שמור במשתנה "var1" במקום בכתובת 5A73:235B , במיוחד כאשר יש לך 10 או יותר משתנים.

המהדר שלנו תומך בשני סוגים של משתנים:
BYTE ו- WORD (בית ומילה).

Syntax for a variable declaration:

         name DB value

         name DW value

DB - stays for Define Byte.
DW - stays for Define Word.

name - can be any letter or digit combination, though it should start with a letter. It's possible to declare unnamed variables by not specifying the name (this variable will have an address but no name).

value - can be any numeric value in any supported numbering system (hexadecimal, binary, or decimal), or "?" symbol for variables that are not initialized.

תחביר להגדרת משתנה:

שם DB ערך

שם
DW ערך

DB - פירושו הגדרת משתנה בגודל בית.
DW  - פירושו הגדרת משתנה בגודל מילה.

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

ערך - יכול להיות כל ערך מספרי בכל שיטת ספירה (בסיס עשרוני, הקסה או בינארי), או הסימן "?" עבור משתנים שלא הוגדרו מראש עם נתון מסוים.

As you probably know from part 2 of this tutorial, MOV instruction is used to copy values from source to destination.
Let's see another example with MOV instruction:

כפי שאתה כבר יודע מ- חלק 2  של הדרכה זו, משתמשים בפקודת ה- MOV להעתקת ערכים ממקור ליעד.
הבה נראה דוגמה אחרת של פקודת ה-
MOV:


#MAKE_COM#
ORG 100h

MOV AL, var1
MOV BX, var2

RET    ; stops the program.
                    עצירה של התוכנית 
VAR1 DB 7
VAR2 DW 1234h
Copy the above code to Emu8086 source editor, and press F5 key to compile and load it in the emulator. You should get something like:

 העתק את הצופן הרשום למעלה לתוך העורך של תוכנת Emu8086, לחץ על כפתור F5 להידור וטען את התוכנית לתוך האמולטור.
אתה צריך לקבל תמונה כמו זו:

As you see this looks a lot like our example, except that variables are replaced with actual memory locations. When compiler makes machine code, it automatically replaces all variable names with their offsets. By default segment is loaded in DS register (when COM files is loaded the value of DS register is set to the same value as CS register - code segment).

In memory list first row is an offset, second row is a hexadecimal value, third row is decimal value, and last row is an ASCII character value.

Compiler is not case sensitive, so "VAR1" and "var1" refer to the same variable.

The offset of VAR1 is 0108h, and full address is 0B56:0108.

The offset of var2 is 0109h, and full address is 0B56:0109, this variable is a WORD so it occupies 2 BYTES. It is assumed that low byte is stored at lower address, so 34h is located before 12h.

You can see that there are some other instructions after the RET instruction, this happens because disassembler has no idea about where the data starts, it just processes the values in memory and it understands them as valid 8086 instructions (we will learn them later).
You can even write the same program using DB directive only:

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

ברשימה של הזיכרון, השורה הראשונה היא אופסט, השורה השנייה היא ערך הקסה, השורה השלישית היא ערך עשרוני, והשורה  האחרונה היא ערך על פי צופן
ASCII .

המהדר לא מבדיל בין אותיות גדולות וקטנות, ו
- "var1" ו- "VAR2" מתייחס לאותו משתנה.

האופסט של
VAR1 הוא 0108h , והכתובת המלאה היא: 0B56:0108.

האופסט של
VAR2 הוא 0109h , והכתובת המלאה היא: 0B56:0109, משתנה זה הינו בגודל מילה, כלומר הוא משתמש ב- 2 בתים. אנו מניחים שהבית הנמוך נשמר בכתובת הנמוכה, כלומר הערך 34h נשמר לפני 12h.

 אתה יכול לראות שיש כמה פקודות נוספות לאחר פקודת ה-
RET, זה קורה כי לתוכנת הפענוח (disassembler) אין אפשרות לדעת היכן מתחילים הנתונים, התוכנה מעבדת את הנתונים שבזיכרון ומקבלת אותם כפקודות תקינות של המעבד 8086 (אותם נלמד בהמשך).
ניתן לכתוב את אותה התכנית תוך שימוש בהוראות מסוג
DB בלבד:

 #MAKE_COM#
 ORG 100h
 
 DB 0A0h
 DB 08h
 DB 01h
 
 DB 8Bh
 DB 1Eh
 DB 09h
 DB 01h
 
 DB 0C3h
 
 DB 7
 
 DB 34h
 DB 12h

Copy the above code to Emu8086 source editor, and press F5 key to compile and load it in the emulator. You should get the same disassembled code, and the same functionality!

As you may guess, the compiler just converts the program source to the set of bytes, this set is called machine code, processor understands the machine code and executes it.

ORG 100h is a compiler directive (it tells compiler how to handle the source code). This directive is very important when you work with variables. It tells compiler that the executable file will be loaded at the offset of 100h (256 bytes), so compiler should calculate the correct address for all variables when it replaces the variable names with their offsets. Directives are never converted to any real machine code.
Why executable file is loaded at offset of 100h? Operating system keeps some data about the program in the first 256 bytes of the CS (code segment), such as command line parameters and etc.
Though this is true for COM files only, EXE files are loaded at offset of 0000, and generally use special segment for variables. Maybe we'll talk more about EXE files later.

העתק את התוכנית הנ"ל לתוך האמולאטור Emu8086 ולחץ על כפתור F5 לביצוע הידור וטעינה בתוכנת ההדמיה.
אתה צריך לקבל את אותו צופן מפוענח, והוא יבצע את אותו הדבר !

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


הפקודה ORG 100h היא הנחייה של המעדר. היא אומרת למהדר איך לטפל בקוד המקורי. הפקודה הזאת היא מאוד חשובה כאשר אתה עובד עם משתנים.
הפקודה מורה למהדר שהקובץ הניתן לביצוע (
com) יוטען בכותבת לוגית 100h, כלומר 256 בתים, לכן המהדר יצטרך לחשב את הכתובת הנכונה לכל המשתנים כאשר יחליף את שמות המשתנים עם האופסטים שלהם. הפקודות לעולם לא הופכות לצופן אמיתי של מכונה.
מדוע הקובץ מוטען בהיסט (קיזוז) של
100h ?  מערכת ההפעלה מחזיקה כמה נתונים של התוכנית ב- 256 בתים הראשונים של מקטע התוכנית (CS) כמו פרמטרים של פקודות מיידיות ועוד.
ההגדרות הקודמות נכונות עבור קבצים מסוג
COM בלבד, קבצים מסוג EXE מוטענים החל מכתובת לוגית 0000 , ובדרך כלל משתמשים במקטעים מיוחדים למשתנים.
בהמשך נדבר יותר לגבי קבצים מסוג
EXE.

Arrays

Arrays can be seen as chains of variables. A text string is an example of a byte array, each character is presented as an ASCII code value (0..255).

Here are some array definition examples:

a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h
b DB 'Hello', 0

b is an exact copy of the a array, when compiler sees a string inside quotes it automatically converts it to set of bytes. This chart shows a part of the memory where these arrays are declared:

מערכים

ניתן להתייחס למערך כשרשרת של משתנים עם אותו שם אבל עם מספר סידורי שונה. מחרוזת של טקסט הינה דוגמה למערך בתים, כל אות מוצגת על פי ערך
ASCII שלה (0...255).

להלן כמה דוגמאות של הגדרת מערכים:

      
a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h
                                          b DB 'Hello', 0


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

 You can access the value of any element in array using square brackets, for example:
MOV AL, a[3]

You can also use any of the memory index registers BX, SI, DI, BP, for example:
MOV SI, 3
MOV AL, a[SI]


If you need to declare a large array you can use DUP operator.
The syntax for DUP:

number DUP ( value(s) )
number - number of duplicate to make (any constant value).
value - expression that DUP will duplicate.

for example:
c DB 5 DUP(9)
is an alternative way of declaring:
c DB 9, 9, 9, 9, 9

one more example:
d DB 5 DUP(1, 2)
is an alternative way of declaring:
d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2

Of course, you can use DW instead of DB if it's required to keep values larger then 255, or smaller then -128. DW cannot be used to declare strings!

The expansion of DUP operand should not be over 1020 characters! (the expansion of last example is 13 chars), if you need to declare huge array divide declaration it in two lines (you will get a single huge array in the memory).

אתה יכול לגשת לכל איבר של המערך באמצאות סוגרים מרובעים לאחר שם המערך, למשל:
                     [
MOV AL, a[3

אתה יכול להשתמש בכל אחד מאוגרי המצביע של הזיכרון:
BX, SI, DI, BP , למשל:
              
MOV SI, 3
         [MOV AL, a[SI


אם אתה צריך להגדיר מערך גדול ניתן להשתמש באופרטור (פקודת מקרו) DUP.
התחביר ל-
DUP :

מספר  DUP  (ערך/ערכים)

מספר: מספר העתקים לביצוע (כל ערך קבוע).
ערך   : ביטוי ש-
DUP יעתיק מספר פעמים.

לדוגמה:

                                   
       (c DB 5 DUP(9
ודרך אלטרנטיבי של ההגדרה:
                                   
        c DB 9,9,9,9,9

דוגמה נוספת:
                                       
(d DB 5 DUP(1,2
ודרך אלטרנטיבי של ההגדרה:
                            
d DB 1,2,1,2,1,2,1,2,1,2

כמובן, אתה יכול להשתמש ב- DW במקום DB , אם נדרש לשמור נתונים גדולים מ- 255 או קטנים מ- -128 . לא ניתן להשתמש ב- DW להגדרת מחרוזות!! (פרט למחרוזת של 2 אותיות).

ההגדרה של האופרנד
DUP  לא יכול להיות גדול מ- 1020 נתונים !! (בהגדרה האחרונה יש 13 נתונים), אם אתה צריך להגדיר מערך גדול מידי וההגדרה עוברת לשורה השנייה, בזיכרון תהיה הגדרה יחידה הכוללת את הכל. 

Getting the Address of a Variable

There is LEA (Load Effective Address) instruction and alternative OFFSET operator.
 Both OFFSET and LEA can be used to get the offset address of the variable.
LEA is more powerful because it also allows you to get the address of an indexed variables. Getting the address of the variable can be very useful in some situations, for example when you need to pass parameters to a procedure.

קבלת כתובת של משתנה
 

קיימת הפקודה LEA (טעינת כתובת אפקטיבי) ופקודה חלופית OFFSET.
ניתן להשתמש בכל אחד מהם:
OFFSET ו- LEA, להשגת הכתובת הלוגית של המשתנה.

פקודת
LEA היא חזקה יותר כי היא מאפשרת קבלת כתובות לוגיות של משתנים ממוספרים.

קבלת כתובת של משתנה יכולה להיות מאוד שימושית בכמה מצבים, למשל כאשר רוצים לעביר פרמטרים לפרוצדורה.

Reminder:
In order to tell the compiler about data type,these prefixes should be used:

BYTE PTR - for byte.
WORD PTR - for word (two bytes).

For example:

BYTE PTR [BX]     ; byte access.
    or
WORD PTR [BX]     ; word access.

Emu8086 supports shorter prefixes as well:

b. - for BYTE PTR
w. - for WORD PTR

sometimes compiler can calculate the data type automatically, but you may not and should not rely on that when one of the operands is an immediate value.

תזכורת:
כדי ליידע את המהדר בנוגע לסוג הנתונים, יש להשתמש בהגדרות המקדימות:

BYTE PTR   להגדרת נתונים בגודל בית.
WORD PTR עבור מילה (שני בתים).

למשל:

BYTE PTR [BX]     ; גישה לבתים.
    או
WORD PTR [BX]     ; גישה למילים.

האמולטור Emu8086 תומך גם בקיצורים:

b. - עבור BYTE PTR
w. - עבור WORD PTR

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

Here is first example:

 להלן דוגמה ראשונה:


 ORG 100h
 
 MOV    AL, VAR1 
             ; check value of VAR1 by moving it to AL.
             ; Var1 בדיקת ערך של
 LEA    BX, VAR1
              ; get address of VAR1 in BX.
              ; VAR1 קבל כתובת של 
 MOV    BYTE PTR [BX], 44h
              ; modify the contents of VAR1.
              ; VAR1 הכנס ערך מספרי חדש בתוך
 MOV    AL, VAR1
              ; check value of VAR1 by moving it to AL.
              ; Var1 בדיקת ערך של
 RET
 VAR1   DB  22h
 
 END
 
Here is another example, that uses OFFSET instead of LEA: להלן דוגמה נוספת, המשתמשת בפקודה OFFSET במקום פקודת LEA.

 ORG 100h

 MOV    AL, VAR1
              ; check value of VAR1 by moving it to AL.
              ; Var1 בדיקת ערך של
 MOV    BX, OFFSET VAR1
             ; get address of VAR1 in BX.
             ; VAR1 קבל כתובת של 
 MOV    BYTE PTR [BX], 44h
              ; modify the contents of VAR1.
              ; VAR1 הכנס ערך מספרי חדש בתוך
 MOV    AL, VAR1
              ; check value of VAR1 by moving it to AL.
              ; Var1 בדיקת ערך של
 RET

 VAR1   DB  22h

 END
Both examples have the same functionality.

These lines:
                    LEA BX, VAR1
                    MOV BX, OFFSET VAR1
are even compiled into the same machine code: MOV BX, num
num is a 16 bit value of the variable offset.

Please note that only these registers can be used inside square brackets (as memory pointers): BX, SI, DI, BP!
(see previous part of the tutorial).

שתי הדוגמאות בעלות אותה פונקציונאליות (מביאות אותנו לאותה תוצאה).

השורות האלה:
                        LEA BX, VAR1
        MOV BX, OFFSET VAR1
מקבלות לאחר ההידור אותו צופן מכונה:
ערך,
MOV BX
הערך הזה הינו משתנה בעל 16 סיביות המהווה קיזוז של כתובת (ערך לוגי).

כפי שהוסבר בתחילת דף ההדרכה, ניתן להשתמש כמצביעים (בתוך סוגרים מרובעות) רק באוגרים:
BX, SI, DI, BP !

Constants

Constants are just like variables, but they exist only until your program is compiled (assembled). After definition of a constant its value cannot be changed. To define constants EQU directive is used:

name EQU <any expression>

For example:

קבועים

הקבועים הם כמו משתנים, אבל הם קיימים רק עד שהתוכנית שלך עוברת הידור (קומפילציה).
לאחר הגדרת קבוע, לא ניתן לשנות את ערכו.
לביצוע ההגדרה משתמשים בפקודת
EQU:

 <כל ביטוי מתמטי>
EQU שם

לדוגמה:

k EQU 5

MOV AX, k
The above example is functionally identical to code: הדוגמה הקודמת גורמת לאותו ביצוע של השורה שבהמשך:
MOV AX, 5
You can view variables while your program executes by selecting "Variables" from the "View" menu of emulator. ניתן לעקוב אחרי המשתנים בזמן ריצת התוכנית. זאת ניתן לבצע על ידי בחירת "משתנים" מתפריט "מראה" של האמולטור.

To view arrays you should click on a variable and set Elements property to array size. In assembly language there are not strict data types, so any variable can be presented as an array.

Variable can be viewed in any numbering system:

  • HEX - hexadecimal (base 16).
  • BIN - binary (base 2).
  • OCT - octal (base 8).
  • SIGNED - signed decimal (base 10).
  • UNSIGNED - unsigned decimal (base 10).
  • CHAR - ASCII char code (there are 256 symbols, some symbols are invisible).

You can edit a variable's value when your program is running, simply double click it, or select it and click Edit button.

It is possible to enter numbers in any system, hexadecimal numbers should have "h" suffix, binary "b" suffix, octal "o" suffix, decimal numbers require no suffix. String can be entered this way:
'hello world', 0
(this string is zero terminated).

Arrays may be entered this way:
1, 2, 3, 4, 5
(the array can be array of bytes or words, it depends whether BYTE or WORD is selected for edited variable).

Expressions are automatically converted, for example:
when this expression is entered:
5 + 2
it will be converted to 7 etc...

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

ניתן לראות משתנים בכל שיטת ספירה:
 
  • HEX - הקסה - בסיס 16.
  • BIN - בינארי - בסיס 2.
  • OCT - אוקטלי - בסיס 8.
  • SIGNED - מסומן (בבסיס 10).
  • UNSIGNED - לא מסומן (בבסיס 10).
  • CHAR - מבוסס בצופן ASCII . קיימים 256 סימנים, מספר סימנים הם בלתי-נראים.

אתה יכול לבצע עריכה של ערך המשתנים בזמן ריצת התוכנית באמולטור, יש לבצע הקשה כפולה מעל שם המשתנה, או לסמן אותו ולהקיש על כפתור "עריכה".

ניתן להכניס מספרים (נתונים) בכל שיטת ספירה, בהקסה חייבים להשתמש בסיומת "
h", בבינארי יש להשתמש סיומת "b", ובאוקטלי יש להשתמש בסיומת "o". מספרים עשרוניים לא צריכים סיומת.
ניתן להכניס מחרוזת בדרך זו:
                               
hello world', 0 '
(מחרוזת זו מסתיימת עם אפס)

ניתן להקליד מערכים בדרך זו:
                            
1, 2, 3, 4, 5

(המערך יכול להיות מערך מספרי או מחרוזתי, הדבר תלוי בהאם המשתנה נבחר כבית או מילה).

ביטויים מוחלפים באופן אוטומטי, למשל:
כאשר הוקלד הביטוי:
                                   2 + 5
זה יהפוך ל- 7 וכו'.

<<< to Part 2 <<   >> to Part 4 >>>

<<< לחלק 4 <<   >> לחלק 2 >>>