האם ההבנה שלי נכונה?
-
בהמשך לפוסט הזה
קיבלתי משוב קצת כועס מר' @dovid בצדק מסוים למרות שלא מסכים עם הכול.
בכל מקרה השקעתי כמו שהוא אמר ולמדתי את הנושא לעומק אני כותב כאן את ההבנה שלי
ואשמח לקבל פידבק ממי שיוכל להשקיע מזמנו ללמד מתכנת מתחיל. אציין שהשקעתי ביומים האחרונים כמה שעות בלמידה ואני לא בא חלילה להטריח.
יש את הקטע קוד הזהglobal main extern GetStdHandle extern WriteConsoleA extern ExitProcess section .bss buffer resb 8 written resd 1 section .text main: push r12 push r13 sub rsp, 40 mov eax, 10000 lea rdi, [rel buffer+7] mov byte [rdi], 10 dec rdi .convert_loop: xor edx, edx mov ecx, 10 div ecx add edx, '0' mov [rdi], dl dec rdi test eax, eax jnz .convert_loop lea rax, [rel buffer+8] sub rax, rdi mov r13d, eax mov r12, rdi mov ecx, -11 call GetStdHandle mov rcx, rax mov rdx, r12 mov r8d, r13d lea r9, [rel written] mov qword [rsp+32], 0 call WriteConsoleA xor ecx, ecx call ExitProcessאני כותב כאן את הדברים לפי סדר השורות ואשמח לתיקון:
- משאיר Main גלוי שיהיה גלוי ללינקר
2/3/4. הצהרה על זה שהקוד מחוץ לקובץ האסמבלי הזה. - מקטע נתונים בלתי מאותחלים
7.שומר תוית בזיכרון בשם באפר של עד 8 בתים
8.שומר דאבל וורד אחד - מקטע הקוד
- התחלת המיין כלומר הקוד
12/13. דוחף את R12 R13 למחסנית לשמור נתונים לפני דריסה של התוכנית - מוריד 40 בתים מRSP (שהוא מצביע על המחסנית) 32 כי זה מוסכמת הABI ועוד 8 לפרמטר חמישי.
- מעביר את 10000 לאוגר EAX
- חיבור של באפר ועוד 7 (סוגריים מרובעים- שווה כתובת) את הערך שומר בRDI
- מעביר תו 10 בגודל ביט אחד לRDI יותר נכון לכתובת עליה הוא מצביע (ירידת שורה)
- מוריד 1 מRDI שהתוכנית לא תדרוס את התו אז מוריד תו אחד למטה
- מתחת הוא כבר עושה לולאה על 10000 מחלק כל פעם ב10 ושומר שארית 0 ואז מחבר בדיוק כמו ש@yossiz היקר הסביר.
עד פה בערך חפרתי וניסיתי להבין אחרי הנזיפה של דוד אציין שלא נעזרתי בAI לכתיבת הפוסט או למושגים כל מה שכתבתי זה ההבנה הטהורה שלי נטו (נכונה או אם לא) באמצעות שבירת ראש וAI
אשמח להארות והערות.
- משאיר Main גלוי שיהיה גלוי ללינקר
-
השורות 18 ו19 מיותרות לחלוטין.
גם את השורות 33, 34 ניתן לפשט
כתבתי מחדש (קימפלתי והרצתי, זה עובד):main: push r12 push r13 sub rsp, 40 mov eax, 10000 lea rdi, [rel buffer+7] .convert_loop: xor edx, edx mov ecx, 10 div ecx add edx, '0' mov [rdi], dl dec rdi test eax, eax jnz .convert_loop lea rax, [rel buffer+8] sub rax, rdi mov r8d, eax mov rdx, rdi mov ecx, -11 call GetStdHandle mov rcx, rax lea r9, [rel written] call WriteConsoleA -
השורות 18 ו19 מיותרות לחלוטין.
גם את השורות 33, 34 ניתן לפשט
כתבתי מחדש (קימפלתי והרצתי, זה עובד):main: push r12 push r13 sub rsp, 40 mov eax, 10000 lea rdi, [rel buffer+7] .convert_loop: xor edx, edx mov ecx, 10 div ecx add edx, '0' mov [rdi], dl dec rdi test eax, eax jnz .convert_loop lea rax, [rel buffer+8] sub rax, rdi mov r8d, eax mov rdx, rdi mov ecx, -11 call GetStdHandle mov rcx, rax lea r9, [rel written] call WriteConsoleA@לי-ראש למרות שהתוכנה שלך עובדת היא פחות נכונה מכמה טעמים
השורות 18 ו19 מיותרות לחלוטין.
הם לא מיותרות אם אתה רוצה ירידת שורה בסוף הפלט.
WriteConsoleAלא מוסיף את הירידת שורה אוטומטית
אומנם המעטפת של ווינדוס כן מוסיפה את הירידת שורה, אבל לא נכון לסמוך על זה, כי מעטפות אחרות מתנהגות בצורה אחרת. יותר נכון לפלוט את הירידת שורה בעצמך אם אתה מעוניין בו (ובאמת תראה שאם תריץ את הקוד של @שאלתיאל יהיו שתי ירידות שורה לפני הפרומפט החדש של המעטפת, אז הקוד שלך לא זהה פונקציונלית לקוד שלו)גם את השורות 33, 34 ניתן לפשט
במקרה מה שעשית עובד, אבל זה לא תקני, כי ב-calling convention של ווינדוס אתה לא יכול לסמוך ש-rad ו-rdx ישמרו על הערך שלהם אחרי חזרה מקריאת פונקציה (הם מה שנקרא volatile registers) והדבר התקני הוא לשמור את ערכן במקום לא volatile לפני הקריאה ל-GetStdHandle
- השמטת את הפרמטר החמישי ל GetStdHandle. אני לא יודע אם בכוונה
mov qword [rsp+32], 0שוב, במקרה לגמרי, הקוד שלך עובד, אבל זה לא תקני
זה עובד כי פה במקרה הערך בסטאק הוא אפס, אבל אי אפשר לסמוך שככה זה יישאר אם נוסיף עוד קוד לתוכנה- השמטת את הקריאה ל-ExitProcess. אני לא יודע אם זה בכוונה, אבל זה גורם לתוכנה לקרוס
-
בהמשך לפוסט הזה
קיבלתי משוב קצת כועס מר' @dovid בצדק מסוים למרות שלא מסכים עם הכול.
בכל מקרה השקעתי כמו שהוא אמר ולמדתי את הנושא לעומק אני כותב כאן את ההבנה שלי
ואשמח לקבל פידבק ממי שיוכל להשקיע מזמנו ללמד מתכנת מתחיל. אציין שהשקעתי ביומים האחרונים כמה שעות בלמידה ואני לא בא חלילה להטריח.
יש את הקטע קוד הזהglobal main extern GetStdHandle extern WriteConsoleA extern ExitProcess section .bss buffer resb 8 written resd 1 section .text main: push r12 push r13 sub rsp, 40 mov eax, 10000 lea rdi, [rel buffer+7] mov byte [rdi], 10 dec rdi .convert_loop: xor edx, edx mov ecx, 10 div ecx add edx, '0' mov [rdi], dl dec rdi test eax, eax jnz .convert_loop lea rax, [rel buffer+8] sub rax, rdi mov r13d, eax mov r12, rdi mov ecx, -11 call GetStdHandle mov rcx, rax mov rdx, r12 mov r8d, r13d lea r9, [rel written] mov qword [rsp+32], 0 call WriteConsoleA xor ecx, ecx call ExitProcessאני כותב כאן את הדברים לפי סדר השורות ואשמח לתיקון:
- משאיר Main גלוי שיהיה גלוי ללינקר
2/3/4. הצהרה על זה שהקוד מחוץ לקובץ האסמבלי הזה. - מקטע נתונים בלתי מאותחלים
7.שומר תוית בזיכרון בשם באפר של עד 8 בתים
8.שומר דאבל וורד אחד - מקטע הקוד
- התחלת המיין כלומר הקוד
12/13. דוחף את R12 R13 למחסנית לשמור נתונים לפני דריסה של התוכנית - מוריד 40 בתים מRSP (שהוא מצביע על המחסנית) 32 כי זה מוסכמת הABI ועוד 8 לפרמטר חמישי.
- מעביר את 10000 לאוגר EAX
- חיבור של באפר ועוד 7 (סוגריים מרובעים- שווה כתובת) את הערך שומר בRDI
- מעביר תו 10 בגודל ביט אחד לRDI יותר נכון לכתובת עליה הוא מצביע (ירידת שורה)
- מוריד 1 מRDI שהתוכנית לא תדרוס את התו אז מוריד תו אחד למטה
- מתחת הוא כבר עושה לולאה על 10000 מחלק כל פעם ב10 ושומר שארית 0 ואז מחבר בדיוק כמו ש@yossiz היקר הסביר.
עד פה בערך חפרתי וניסיתי להבין אחרי הנזיפה של דוד אציין שלא נעזרתי בAI לכתיבת הפוסט או למושגים כל מה שכתבתי זה ההבנה הטהורה שלי נטו (נכונה או אם לא) באמצעות שבירת ראש וAI
אשמח להארות והערות.
@שאלתיאל בגדול נראה שהבנת נכון.
האם משהו מפריע לך עדיין בקוד הזה?אסביר לך מה התכוונתי בנושא הקודם שהקוד מיותר: התכוונתי שאם אתה יודע מראש שאתה רוצה להדפיס את המספר 1000, אז במקום להמיר אותו ממספר בינארי למחרוזת עם הלולאה הזאת, אפשר פשוט מראש להכניס לקוד את המחרוזת "1000" (כמו שעשית עבור לינוקס)
אבל מכיוון שהקוד הזה הוא רק ללמידה, אין בעיה שהקוד עושה פעולות מיותרות, העיקר שאתה לומד מהם.אגב,
יש באג בקוד שלך (וגם @לי-ראש לא תיקן אותו)
אחרי סוף הלולאה, המצביע rdi לא מצביע על תחילת המחרוזת, אלא בית אחד לפני התחילה. למה? כי בכל איטרציה של הלולאה אתה מוריד 1 מ-RDI, גם באיטרציה האחרונה
התוצאה היא שאתה מדפיס 7 בתים לקונסול במקום ה-6 שהתכוונת להדפיס ("10000\n")
בפועל, ההתנהגות של הקונסול של ווינדוס הוא שהוא מתעלם מפלט של null bytes אז זה לא מזיק, אבל זה לא תקניעוד הערה:
בתוכנה שכתבת עבור לינוקס אתה משתמש בפונקציה write כדי לכתוב ל-stdout
פה אתה משתמש בWriteConsoleA שהוא כותב רק לקונסול, לא לכל stdout שמוגדר
בתוכנות שורת פקודה יותר נפוץ להשתמש בWriteFile כדי שזה יעבוד על כל סוגי stdout, אלא אם כן מסיבה כלשהו אתה בדוקא רוצה לכתוב רק לקונסול.
(עיין כאן ליותר הסבר על stdout) - משאיר Main גלוי שיהיה גלוי ללינקר
שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.
נמאס לכם לגלול בין אותם הפוסטים בכל ביקור? כשנרשמים לחשבון, תמיד תחזרו בדיוק למקום שבו הייתם קודם, ותוכלו לבחור לקבל התראות על תגובות חדשות (בין אם במייל, ובין אם בהתראת פוש). תוכלו גם לשמור סימניות ולפרגן ב-upvote לפוסטים כדי להביע הערכה לחברי קהילה אחרים.
בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗
הרשמה התחברות