פקודת move באסמבלי ושיטות מיעון

white shadow 3

New member
פקודת move באסמבלי ושיטות מיעון

משהו קטן שהבנתי שאני כנראה לא ממש מבין...

נניח ויש לי פונקציה שנקראת ע"י פונקציה אחרת, ונניח כי חתימתה היא:
zz a [int A[], int b] zz
אז ממה שאני מבין, ב-frame של הפונקציה הקוראת יהיה שדה של b, אחד מתחתיו את הכתובת של האיבר הראשון ב-A, אחת מתחתיו return address ואז מתחיל ה-frame של הפונקציה a...
נניח אני כותב את הפקודה:
zz movl 8(R8), R2 zz
(נניח שהימני זה היעד והשמאלי זה המקור..)
האם זה מעביר לרגיסטר R2 את הכתובת של האיבר הראשון ב-A או את הערך שלו?
כי בגדול אני יודע ש-() זאת גישה לתוך זיכרון (כלומר ללכת לדוגמא לכתובת שבתוך R8ּּ+8 ואז ללכת לתא בזיכרון שמוצבע ע"י הכתובת הזו, אבל אז - אם אני רוצה שרגיסטר מסויים יכיל את כתובת האיבר הראשון במערך ולא את הערך שלו - איך אני מבצע זאת?
(מקווה שלא עשיתי סלט מכל הדברים האלה
)

תודה!
 

BravoMan

Active member
בהנחה ש-R8 אצלך הוא stack pointer

ולא בוצע reserve על ה-stack עבור פונקציה a, והמערכת היא 64 ביט, בכתובת R8 + 8 יהיה הפרמטר האחרון שנדחף (בדוגמה שלך ראשון משמאל) שהוא במקרה כתובת של מערך.
&nbsp
והכתובת הזו היא שתכנס ל-R2.
 

white shadow 3

New member
אוקי, חושב שמתחיל אולי קצת להתחבר לי


בעצם גם כאן מתבצע dereference (מה שהסוגריים () מבצעים) --> הולכים לכתובת של R8, מוסיפים לה 8, מסתכלים על מה שיש בתוך התא עם הכתובת שמצאנו - ומה שיש שם זאת בעצם הכתובת של האיבר הראשון במערך

עכשיו שאלה נוספת: נסתכל על מערך של int
נניח ששמרתי את הכתובת של האיבר הראשון במערך ברגיסטר R3.
וברגיסטר R4 יש לי ערך שמור כלשהוא (נגיד 4)
אז אם אני מבצע את השורה:
zz move R4, (R3) zz
אני בעצם כותב לתוך האיבר הראשון במערך את הערך 4, נכון?

עכשיו נניח אני מגדיל את ערכו של הערך ב-R4 ב-1, ולאחר מכן מבצע את השורה
zz move R4, 4(R3) zz
אז אני בעצם כותב לאיבר השני במערך את הערך 5
זה נכון?

לאחר השורה האחרונה - מה הערך בתוך R3? האם הוא נשאר כמו מקודם ומכיל את הכתובת של האיבר הראשון, או שעצם הכתיבה
zz 4(R3) zz
גם שומרת בתוך R3 את הכתובת של האיבר *השני* במערך? (כלומר יהיה את הכתובת שהייתה מקודם + 4)

(שוב) תודה רבה על הסלבנות וההסברים!
 

BravoMan

Active member
אני לא מכיר את גרסת האסמבלי הספציפית

שלימדו אתכם וזה לא נראה כמו סינטקס סטנדרטי של AT&T או Intel.
&nbsp
בד"כ, ציון כתובת מהסוג שהדגמת - תוכן רגיסטר בתוספת קבוע, אינו מבצע פעולה על הרגיסטר ששומר את הכתובת, ואינו משנה את הכתובת השמורה.
&nbsp
כמובן, אם ממציאים אסמבלר תאורטי, אפשר ליצור פעולה כזו, אבל אני לא בטוח כמה שימושית היא תהיה...
&nbsp
מה שכן - זכור תמיד ש-"כתובת" זה בסה"כ מספר, שאינו שונה משום ערך מספרי אחר כל עוד אתה בתור מתכנת לא קובע שהמשמעות שלו זה "כתובת".
מבחינת מעבד, הכל מספרים - גם פקודות, גם כתובות, גם מידע מכל הסוגים.
 

white shadow 3

New member
אברר את הנושא הזה עם המורה...

דבר נוסף, כאשר םונקציה קוראת לפונקציה אחרת אז למדנו שהמשתנים שהפונקציה הנקראת אמורה לקבל מובאים לסוף ה-frame של הפונקציה הקוראת, לאחר מכן מעודכן return address ולאחר מכן נפתח frame חדש של הפונקציה הנקראת, שמתחיל ב-R8 שמבציע על תחילת ה-frame ובאותו זמן גם R7 ו-R8 מצביעים על אותו תא (R7 זה הרגיסטר ששומר את הכתובת הבאה לכתיבה) נניח ששניהם מצביעים על התא 0x108
&nbsp
משהו שלא כ"כ הבנתי מבחינת סדר ומהלך האירועים:
נניח הפונקציה מבצעת כמה פעולות שחלקן נניח כותבות במחסנית, תוך כדי כך R7 מתחיל לרדת למטה (אחרי שחלק מהדברים נכתבו במחסנית) ואז הפונקציה בשלב כלשהוא מגיעה לסופה:
(מבחינת תרגום לקוד c - מה בפועל קורה כאשר אנחנו עוברים את ה-{ שסוגר לנו את הפונקציה?)
איך מתבצע התהליך הזה שבו ה-frame "מתקפל" (?) חזרה כלפי מעלה והתוכנית חוזרת ל-return address? (שאני מניח הוא הכתובת של המקום שבו "עצרנו" את הפונקציה הקודמת וקראנו לפונקציה הנוכחית..)
האם תוך כדי התהליך הזה המערכת מוחקת את כל הערכים שהיו בזיכרון החל מהתא שהיה קודם מוצע ע"י R7 ועד לתא של הreturn address, או שהיא פשוט קובעת את R7 להצביע עליהם וכביכול "מאשרת" שאלו כעת תאים מותרים לכתיבה?
&nbsp
thanks!
 

BravoMan

Active member
המערכת לא קשורה לזה.

מה שקורה בתוך התוכנה שלך - מעבר בין פונקציה אחת לשנייה, אינו עניינה של מערכת הפעלה.
&nbsp
אף אחד לא מוחק ערכים בזיכרון המחשב אם הדבר לא נכתב ספציפית ע"י המתכנת.
כשהתוכנה שלך מתחילה לרוץ, היא מקבלת stack מסוים, וכשאתה עובד ברמת אסמבלי, זו אחריות שלך כמתכנת לנהל אותו.
&nbsp
בד"כ, עובדים איתו סדרתית ע"י PUSH ו-POP, אבל בתחלס אין דבר שמונע גישה אקראית למיקום כלשהו ב-stack ע"י מיון רגיל של כתובת בזיכרון.
&nbsp
כשחוזרים מפונקציה, ערכי הריגיסטרים שקשורים לריצה כגון מצביע הפקודה (IP) ומצביע המחסנית (SP), אמורים לאחזור לערכים בהם היו לפני קריאה לפונקציה.
מי אחראי להחזיר אותם לשם?
או הפונקציה הקוראת, או הפונקציה הנקראת, כמו שהסברתי לך קודם - זה תלוי ב-calling convention.
&nbsp
אבל, זה לא קורה באורח פלא. איפשהו שאתה בתור מתכנת צריך לשים את ההוראות לבצע את זה.
&nbsp
שים לב שסוגר מסולסל בשפת C לא באמת מתורגם למשהו בשפת אסמבלי.
זו לא שורה שמתבצעת.
זה רק סימן בשביל המתכנת והקומפיילר לבודד בלוק של קוד מבחינה לוגית.
 

white shadow 3

New member
אני אשנה קצת את השאלה.

אם נחזור לשאלה מהפוסט הקודם:
http://www.tapuz.co.il/forums/viewmsg/89/180775231/מחשבים/שפות_תכנות
&nbsp
לגבי הסעיף (**)
שנינו כתבנו כי במחסנית לאחר השורה (**) יהיה המשתנה x וכתובת חזרה ל-a..
&nbsp
רק לוודא שהבנתי נכון:
תמונת המחחסנית תראה משהו כזה :
y
x
return address (to the func who called a
x
return address to a
&nbsp
השאלה שלי - אם אני צריך לכתוב במצב הזה לאן מבציע R8 ו-R7, מה יהיה "נכון" להגיד?
 

BravoMan

Active member
תזכיר לי בבקשה מה התפקיד של R8 ו-R7

במעבד שלך?
&nbsp
אם אחד מהם הוא SP (R7 ?) אאל"ט הוא יצביע על תא אחד אחרי return address to a.
לגבי השני, לא ברור מה תפקידו.
 

white shadow 3

New member
תגובה

R7 מצביע לי על הכתובת הבאה לכתיבה במחסנית
R8 מצביע כל ההתחלה של ה-frame של הפונקציה בה אני נמצא..(שגם כאן אני יכול לשאול - כאשר אני ב-frame של פונק, שקוראת לפונק' נוספת ונפתח frame חדש עבורה, אז R8 פשוט מצביע לתא אחד אחרי ה-return address וזהו? אין בו משהו מיוחד נוסף?)
 

BravoMan

Active member
"משהו מיוחד נוסף"?

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

white shadow 3

New member
ושאלה אחרונה (מקווה
) למקצה זה:

בוקר טוב,
אמרו לנו בשיעור שכאשר פונקציה אחת קוראת לפונקציה אחרת עם פרמטרים, אז הפונקציה הקוראת מביאה את הפרמטרים לסוף ה-frame שלה ולאחר מכן תבוא כתובת החזרה.
&nbsp
האם זה קורה כך *תמיד* ובדיוק כך עלי "לתאר" את מצב המחסנית כאשר אני רואה מצב כזה, או שיש מקרים שלא?
כלומר אם פונקציה א' קוראת לפונקציה ב' עם פרמטר x אז יופיע במחסנית:
x
return address
R8
&nbsp
(ו-x משתנה או לא משתנה במהלך פונקציה א')
ואז פונקציה ב' קוראת לפונקציה ג' עם פרמטר x (ואולי עוד..)אז יופיע במחסנית:
x
return address
x
return address
R8
(ו-x משתנה או לא משתנה במהלך פונקציה ב')
&nbsp
ואז פונקציה ב' קוראת ל-ג' עם פרמטר x (ואולי עוד) וכן הלאה....
?
&nbsp
תודה
 

BravoMan

Active member
אם אמרו לכם, אז תמיד...

כמו שכבר כתבתי לך, בעולם האמתי יש מספר "calling conventions", כלומר, מספר שיטות לקרוא לפונקציה ולהעביר פרמטרים.
https://en.wikipedia.org/wiki/X86_calling_conventions
&nbsp
כך למשל, לפעמים ישמר במחסנית תוכן אוגרים כלליים לפני הקריאה, כדי שהפונקציה הנקראת תוכל להשתמש באוגרים ולדרוס את התוכן שלהם בלי חשש, ואחרי שחוזרים לפונקציה הקודמת היא תוכל לשחזר את המצב שלה לפני הקריאה.
&nbsp
אבל, לצורך פתרון התרגילים שתקבל בקורס, אליך להיצמד ל-calling convention שנתנו לכם, ושאותו תיארת כאן.
 
למעלה