לפני – ריבונו של עולם, שנה וחודש! – הבטחתי להסתכל לתוך זיכרון ה-Flash של מיקרו-בקר מצוי כדי למדוד את מידת האקראיות של קוד המכונה שנצרב עליו, ולבדוק אם אפשר להשתמש בו כמקור למספרים פסודו-אקראיים. הפוסט הזה לא יסגור את המעגל לגמרי, אבל כן ייתן לנו כמה כלים מעניינים…
התמונה למעלה מורכבת מבייטים אמתיים ששלפתי מזיכרון ה-Flash (איפה שקוד התוכנה מאוחסן) של ארדואינו Duemilanove פעיל. פרט למסגרת הכחולה שהתווספה בדיעבד, כל ריבוע קטן (2×2 פיקסלים על המסך, כשהתמונה בגודל מלא) הוא בייט יחיד, לפי סדר הבייטים בזיכרון, משמאל לימין ומלמעלה למטה. ערכי הבייטים מיוצגים על ידי גוון הפיקסל, החל משחור ל-0 ועד לבן ל-255.
את השליפה ביצעתי בעזרת המאקרו pgm_read_byte_near שמוגדר בקומפיילר, ושמסוגל לקרוא בייט מכל כתובת שהיא ב-Flash. הנה קוד הארדואינו המלא:
void setup() { Serial.begin(9600); while (!Serial.available()); } void loop() { uint16_t x; byte b; for (x = 0; x < 32768; ++x) { b = pgm_read_byte_near(x); Serial.write(b); } while (1); }
הוא מחכה לקלט כלשהו בסריאל, שופך דרכו את כל 32,768 הבייטים שבזכרון ה-Flash של ה-ATmega328P, ונכנס ללולאה אינסופית כדי להשתתק.
את הקריאה של המידע והצגתו בצורה גרפית – כולל החלוקה השרירותית לשורות באורך קבוע – ביצעתי בעזרת סביבת הפיתוח החמודה והנוחה-בדיוק-לדברים-כאלה BASIC256. כך זה נראה בשטח (לחצו להגדלה):
אז מה הייצוג הוויזואלי הזה אומר לנו?
קודם כל, אנחנו רואים שהקוד מחולק, בצורה שאינה משתמעת לשתי פנים, לשני חלקים עיקריים נפרדים, שביניהם חלל גדול מלא בערכי 255 רצופים. ניחשתם נכון – החלק התחתון הוא ה-Bootloader של הארדואינו, שממוקם בכתובות הגבוהות בזיכרון.
ממצא פחות בולט במבט ראשון, אך מעניין עוד יותר, הוא הגודל של החלק העליון. אם תקמפלו את קוד הארדואינו שלמעלה, סביבת הפיתוח תגיד לכם שהוא תופס 1706 בייטים, אבל אפילו בהערכה גסה ביותר רואים שהחלק העליון תופס הרבה יותר מזה – למעשה, למעלה מ-12K בייטים. הקוד העודף אינו אלא שיירים מתוכנות קודמות שנצרבו על המיקרו-בקר: סביבת הפיתוח של ארדואינו לא טורחת למחוק את כל ה-Flash בעת צריבה של קוד חדש. מה שכן אפשר לראות הוא רווח קטן יחסית בין הקוד הנוכחי לבין השאריות מקודם, ונדרש תחקיר נוסף כדי לגלות מהו – האם "טווח ביטחון" שמתווסף אוטומטית, או אזור שמוקדש לאתחול ערכי משתנים, או משהו אחר.
עד כמה הקוד הזה קרוב לאקראיות? בואו נסתכל תחילה על התפלגות ערכי הבייטים. גזרתי ברמת דיוק סבירה את ה"קוד" מהתמונה ונתתי לתוכנת הציור לבדוק מה הפרופורציה של כל ערך:
פיזור ערכי הבייטים, שהם הפקודות השונות בשפת המכונה, לחלוטין לא אחיד. גם אם נתעלם מ-255 החריג (כ-60% מהזיכרון, ושימו לב שההיסטוגרמה נקטעת בחלקה העליון), קל לראות שערכים מסוימים כמעט אינם מופיעים כלל, ואילו אחרים נפוצים מאד. כבר עכשיו, עוד לפני שדיברנו על דפוסים ניתנים-לחיזוי של פקודות, ברור שחילוץ ערכים פסודו-אקראיים מהקוד הגולמי ידרוש מספר חישובים נוספים, מהם רצינו להימנע מלכתחילה. כך שלא מצאנו פתרון קסם, ומצד שני, אקראיות חלשה שכזו אולי כן תספיק למשחקים ולאפקטים גרפיים מסוימים.
אני חושב שזו ויזואליזציה ממש מגניבה של המידע הזה. אני לא חושב שראיתי פעם את המידע בשבב מוצג בכזו דרך מעניינת. בעיני אגב כדאי לשים בדיאגרמת עוגה את 5-8 המילים הנפוצות ביותר בקוד ואת השכיחות שלהן.
חשבת לברר מה המשמעות של המילים הנפוצות בשפת המכונה של AVR?
רוב הפקודות ב-Instruction Set כוללות גם פרמטר אחד או שניים, כך שחלק גדול מהבייטים בתמונה שייכים בעצם לא לפקודות אלא לפרמטרים. כל הפעולות המתמטיות/לוגיות מבוצעות על רגיסטרים פנימיים, ויש המון העברות מידע מה-SRAM לרגיסטרים האלה ובחזרה. לכן, כשהתחלתי לחשוב על תשובה לשאלה שלך, הנחתי שהבייטים הכי נפוצים בקוד יהיו כתובות של רגיסטרים – מצד שני, הכתובות שלהם אמורות להיות נמוכות מאד… הסתכלתי ב-AVR Instruction Set Manual, והסתבר ששפת המכונה בנויה מיחידות של 16 ביט (בדרך כלל), שבתוכן הפקודות והפרמטרים מופרדים ברמת הביטים, לא הבייטים! כלומר, צריך לבצע ניתוח עמוק מאד כדי לזהות את הפקודות הנפוצות בשיטה כזו. יש לי כמה… לקרוא עוד »
ההימור שלי על הרווח הוא הצורה שבה ממומש זיכרון פלאש. לרוב הוא מגיע ב"עמודים" שצריך למחוק כגוש ואז ניתן לכתוב עליהם מחדש. בטח מחקו עד סוף הגוש ואז כתבו כמה שהיה צורך.
נראה לי שאתה צודק. בדקתי וגודל ה-Page ב-ATmega328P הוא 64 בייטים – רבע שורה בתמונה שלי – והערך של תאים "מחוקים" ב-Flash הוא 255 (לבן בתמונה). עכשיו חסר רק ההסבר לכל הדפוסים האחידים-יחסית של שחור ולבן שנמצאים באותו איזור.