מה זה Endianness - סדר הבייטים שאף אחד לא מסביר טוב¶
יש מושגים בעולם הליבה של המחשב שנשמעים מסובכים אבל בפועל פשוטים מאוד ברגע שמישהו מסביר אותם נכון. Endianness הוא בדיוק כזה. זה מונח שגורם לאנשים רבים לפחד ממנו, אבל הרעיון שמאחוריו הוא כמעט טריוויאלי - השאלה היא רק באיזה סדר שומרים בייטים בזיכרון. הבעיה היא שהפרט הקטן הזה יכול לגרום לבאגים מטורפים אם לא שמים לב אליו, במיוחד כשמנתחים דאמפים או עובדים עם רשת.
אז מה זה בעצם Endianness¶
מספר שגדול מבייט בודד, כמו מספר בגודל 32 סיביות, תופס כמה בייטים ברצף בזיכרון. השאלה שEndianness עונה עליה היא - איזה בייט מהמספר שומרים ראשון, בכתובת הזיכרון הנמוכה ביותר? האם שומרים את הבייט הכי משמעותי קודם, או את הבייט הכי פחות משמעותי קודם?
קחו לדוגמה את המספר ההקסדצימלי 0x12345678. אם רוצים לשמור אותו בזיכרון, יש שתי דרכים הגיוניות לעשות זאת, וכל אחת מהן היא convention שונה שמעבד יכול לבחור.
ההבדל בין Little Endian ל-Big Endian¶
בשיטת Little Endian, הבייט הכי פחות משמעותי נשמר בכתובת הזיכרון הנמוכה ביותר. במספר שלנו, 0x78 יישמר ראשון, ואחריו 0x56, אחר כך 0x34, ולבסוף 0x12. זה נשמע הפוך לאינטואיציה בהתחלה, אבל זו בדיוק השיטה שמעבדי x86 ו-x86-64 עובדים בה, כלומר כמעט כל מחשב אישי ורוב השרתים בעולם.
בשיטת Big Endian, קורה ההפך - הבייט הכי משמעותי נשמר ראשון, בסדר שמרגיש "טבעי" יותר לבני אדם, כי הוא זהה לאיך שאנחנו כותבים מספרים - מהספרה המשמעותית ביותר לפחות משמעותית. חלק מארכיטקטורות המעבדים ההיסטוריות עבדו כך, וגם היום יש התקנים ופרוטוקולים שמשתמשים בשיטה הזו.
למה זה בכלל משנה למישהו שלא כותב דרייברים¶
השאלה ההגיונית שעולה היא - אם רוב המחשבים שאנחנו נוגעים בהם הם Little Endian, למה בכלל להתעסק בזה? התשובה היא שברגע שאתם עוברים את גבולות המעבד הבודד שלכם, Endianness צץ בכל מקום.
הדוגמה הכי נפוצה היא רשתות. פרוטוקולי תקשורת, כולל חלקים משמעותיים בפרוטוקול TCP/IP, מגדירים שהנתונים שעוברים ברשת נשלחים בסדר Big Endian, שנקרא גם "network byte order". המשמעות היא שכל תוכנה שכותבת או קוראת נתונים ישירות מחבילות רשת חייבת להמיר בין הסדר הפנימי של המעבד לסדר שהרשת מצפה לו, אחרת המספרים שתקבלו יהיו פשוט שגויים, בלי שום הודעת שגיאה שתרמוז לכם למה.
דוגמה נוספת היא קבצים בינאריים. פורמטים רבים של קבצים מגדירים במפורש באיזה סדר בייטים לשמור שדות מספריים, ותוכנה שקוראת את הקובץ חייבת לדעת את הסדר הזה כדי לפרש נכון את הנתונים. אם תכתבו פרסר לפורמט קובץ ותתעלמו מהעניין הזה, תקבלו מספרים הזויים לגמרי בלי שתבינו למה.
הנדסה לאחור ו-Endianness¶
מי שעוסק בניתוח דאמפים בהנדסה לאחור נתקל בEndianness כמעט כל הזמן. כשמסתכלים על דאמפ זיכרון גולמי בכלי כמו דיבאגר, רואים רצף של בייטים, ואם לא זוכרים באיזה סדר המעבד שומר מספרים, קל מאוד לקרוא ערך לגמרי לא נכון.
הטעות הנפוצה בדיבאגר¶
הטעות הכי שכיחה קורית כשמישהו מסתכל על דאמפ זיכרון גולמי ומנסה לקרוא אותו "כמו שכתוב", בייט אחרי בייט, בלי לזכור שעל מעבד Little Endian צריך להפוך את סדר הבייטים כדי לקבל את הערך המספרי האמיתי. תראו רצף בייטים כמו 78 56 34 12, ואם תקראו אותו כמו שהוא כתוב תקבלו מספר לגמרי שונה מהמספר 0x12345678 שבאמת מיוצג שם.
הטעות הזו מסוכנת במיוחד כשמנתחים מבני נתונים מורכבים, כמו כתובות בזיכרון או קוד shellcode, כי טעות בפירוש הכיוון יכולה לגרום לכם לפספס לגמרי מה קורה, או גרוע מכך, להסיק מסקנה שגויה על התנהגות התוכנה. כלי דיבאג טובים בדרך כלל מציגים ערכים כבר "מפורשים" נכון, אבל ברגע שאתם מסתכלים על זיכרון גולמי בעצמכם, זו אחריותכם לזכור את הכיוון.
למה זה חלק חשוב מהבנת המחשב מהיסוד¶
המושג הזה, Endianness, הוא דוגמה מצוינת לכך שאותו מידע לוגי - מספר - יכול להיות מיוצג פיזית בכמה דרכים שונות, ושהבחירה הזו לא שרירותית אלא נקבעת על ידי החומרה או הפרוטוקול. בקורס ליבת המחשב אנחנו עוברים על הבסיס הזה יחד עם עוד הרבה פרטים קטנים כאלה, שברגע שמבינים אותם, כל שאר עולם המחשוב ברמה הנמוכה נהיה הרבה יותר ברור.
לסיכום¶
המונח Endianness מתאר פשוט את השאלה באיזה סדר מעבד שומר את הבייטים של מספר רב-בייטי בזיכרון, Little Endian או Big Endian. זה נשמע כמו פרט טכני זניח, אבל הוא צץ בכל מקום שבו נתונים עוברים בין מערכות שונות - רשתות, קבצים בינאריים, וניתוח דאמפים בדיבאגר. מי שמבין את זה נכון פעם אחת, לא יטעה בזה שוב.
בואו תבנו את הבסיס הזה נכון, עם קהילה שכבר שם.