facebook pixel בלוג: קובץ HEX ואיך יודעים לאן לקפוץ - www.4project.co.il
Main logo www.4project.co.il
כל הרכיבים לפרוייקט שלכם
עגלת קניות

העגלה ריקה

לקוחות נכבדים, אלה שעות הפעילות של המחסן במהלך פסח 2024:
ערב חג וחג ראשון (22-23/04) - המחסן סגור
חול המועד (24-25/04) - המחסן יפעל בין 8:00 עד 15:00
ערב חג וחג שני (28-29/04) - המחסן סגור
נחזור לפעילות רגילה ביום שלישי 30/04
חג שמח!

קובץ HEX ואיך יודעים לאן לקפוץ


2022-08-26 14:31:24
ממשיך את החפירות באיזורי הזכרון של כרטיס Seeeduino XIAO שהתחלתי בפוסט הקודם...

אחרי שפרסמתי את הפוסט נשארו לי בראש 2 שאלות עיקריות:
  1. איפה הנתונים שאצטרך לשלוח לכרטיס כדי לצרוב גרסה?
  2. מה קורה במעבד אחרי ה-bootloader, האם פונקציית ה-main יושבת בכתובת הראשונה ב-FLASH?

ובכן, בפוסט זה אמצע את התשובות על שתי השאלות.

איפה הנתונים שאצטרך לשלוח לכרטיס כדי לצרוב גרסה?

הסתכלתי על הפלט של הקומפילציה, ראיתי שהוא בונה קבצים שונים, כולל קבצי ה-test שלי ולבסוף רושם שהוא מייצר קובץ ELF וגם קובץ BIN:
קוד: בחר הכל
Linking .pio/build/seeed_xiao/firmware.elf
Checking size .pio/build/seeed_xiao/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [=         ]   7.3% (used 2408 bytes from 32768 bytes)
Flash: [          ]   4.2% (used 11120 bytes from 262144 bytes)
Building .pio/build/seeed_xiao/firmware.bin


חיפוש של "wiki elf file format" בגוגל הביא לי שני מדריכים מעולים:
Executable and Linkable Format
Understanding the ELF File Format

אין ספק שיש שם את כל מה שאני צריך, אבל זה נראה לי קצת מסובך מדי. אני זוכר שצריך להיות קובץ HEX בפורמט פשוט יותר של כתובות לצריבה ונתונים מה לצרוב...
ממשיך לחפש איך גורמים ל-PlatformIO לייצר קובץ HEX. על קובץ BIN ויתרתי מראש כי זה יכול להיות בכל פורמט שתרצו, ולא מצאתי שום רמז בכותרת של ההקובץ מה זה יכול להיות.

אז חיפוש בגוגל הביא אותי לדוגמאות איך לייצר קובץ HEX, מסתבר שאפשר להוסיף סקריפט Python שיתבצע לפני או אחרי תהליך הקומפילציה, שם תוכלו לעשות מה שתרצו, כולל להורות לסביבה לייצר קובץ HEX. אז הוספתי קובץ extra_post.py עם תוכן הבא (הכל מתוך הדוגמה מהרשת):
קוד: בחר הכל
Import("env")
# Custom HEX from ELF
env.AddPostAction(
    "$BUILD_DIR/${PROGNAME}.elf",
    env.VerboseAction(" ".join([
        "$OBJCOPY", "-O", "ihex", "-R", ".eeprom",
        "$BUILD_DIR/${PROGNAME}.elf", "$BUILD_DIR/${PROGNAME}.hex"
    ]), "Building $BUILD_DIR/${PROGNAME}.hex")
)


וצריך גם להוסיף שורה נוספת ל-platformio.ini של הפרוייקט:
קוד: בחר הכל
extra_scripts = post:extra_post.py


קומפילציה נוספת ועכשיו גם קובץ HEX נוצר כחלק מה-build:
קוד: בחר הכל
Linking .pio/build/seeed_xiao/firmware.elf
Building .pio/build/seeed_xiao/firmware.hex
Checking size .pio/build/seeed_xiao/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:  [=        ]  7.3% (used 2408 bytes from 32768 bytes)
Flash: [          ]  4.2% (used 11120 bytes from 262144 bytes)
Building .pio/build/seeed_xiao/firmware.bin


בואו נראה מה יש לנו בקובץ HEX:
קוד: בחר הכל
:1020000000800020DD340000C5340000C53400002D
:1020100000000000000000000000000000000000C0
:10202000000000000000000000000000C5340000B7
:102030000000000000000000C53400003135000041
:10204000C5340000C5340000C5340000C5340000AC
:10205000C5340000C5340000C5340000C934000098
:10206000C5340000C5340000C5340000C53400008C
:10207000C5340000B13C0000C5340000C534000088
:10208000C5340000C5340000C5340000C53400006C
:1020900000000000C5340000C5340000C534000055
:1020A000C5340000C5340000C5340000C53400004C
...


עדיין לא ברור מה זה, אבל נראה שמתקרבים... רואים שיש שם איזה שהוא פורמט, יש מספרים רצים שנראים כמו כתובת (2000, 2010, 2020 וכו'). מזכיר שבפוסט הקודם מצאנו שהקוד ב-FLASH מתחיל בכתובת 0x2000, שזה 8K וזהו הגודל שהשאירו ל-bootloader:
קוד: בחר הכל
MEMORY
{
  FLASH (rx) : ORIGIN = 0x00000000+0x2000, LENGTH = 0x00040000-0x2000 /* First 8KB used by bootloader */
  RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}


חיפוש של "wiki hex file format" בגוגל מביא אותי להסבר מעולה של ויקיפדיה על הפורמט: 
Intel HEX

מצאתי גם תוסף ל-VSCode בשם "Intel HEX format" שמסמן בצבעים את האיזורים השונים של כל שורה, מה שמקל מאוד להסתכל על תוכן הקובץ. מרפרוף בקובץ, לקראת הסוף מצאתי את הדברים המעניינים האלה:
"תוכן קובץ HEX"

השורה הרביעית בתמונה, עם קוד 02 בירוק, שלפי המדריך מציין את כתובת הייחוס לנתונים שבאים אחריו. צריך להכפיל ב-16h את הכתובת שרשומה, כלומר כתובת בסיס היא 0x30000. שורות הבאות הן עם כתובות 0x8000, 0x8010, 0x8020, כלומר הכתובות המלאות לשורות אלה הן 0x38000 והלאה, ואחריו שורות נוספות עם כתובות 0xC000, 0xC010, 0xC020, כלומר הכתובות המלאות לשורות אלה הן 0x3C000... ומה הכתובות האלה? אלה הכתובות של איזורי ה-FLASH שהגדרתי בפוסט הקודם וגרמתי לפונקציות הבדיקה להיכנס לכתובות אלה:
קוד: בחר הכל
.func1          0x0000000000038000       0x24
*test1.cpp.o(.text .text*)
.text._Z9testFunc1v
                0x0000000000038000       0x24 .pio/build/seeed_xiao/src/test1.cpp.o
                0x0000000000038000                testFunc1()
*test1.cpp.o(.rodata*)

.func2          0x000000000003c000       0x24
*test2.cpp.o(.text .text*)
.text._Z9testFunc2v
                0x000000000003c000       0x24 .pio/build/seeed_xiao/src/test2.cpp.o
                0x000000000003c000                testFunc2()
*test2.cpp.o(.rodata*)


קובץ HEX הוא בהחלט הפורמט שצריך לשלוח לכרטיס כדי לעדכן גרסה. חשבתי על הנושא קצת אחרי הפוסט הקודם ונראה שצריך לשלוח את אותה גרסת התוכנה כשהיא מקומפלת לאיזור זכרון ראשון וגם גרסה שמקומפלת לאיזור זכרון השני ולתת לכרטיס לבחור את הגרסה שהוא צריך. אפשר יהיה לאחד את השורות של איזור זכרון 1 ושורות של איזור זכרון 2 לתוך קובץ אחד ולשלוח אותם יחד. אפשר לדלג על כל שאר הנתונים בקובץ. אכנס לפרטים לגבי זה בפוסט הבא.

אז על שאלה הראשונה יש לנו תשובה. בואו נמשיך לשאלה השניה:

מה קורה במעבד אחרי ה-bootloader?


מי שמכיר את עולם התכנות רק דרך המשקפיים של Arduino, יכול לחשוב שהקוד מתחיל לרוץ בפונפציית ()setup, אבל זה לא נכון, גם בקומפילציה של קוד ארדואינו יש פונקציית ()main, שהיא בדרך כלל הפונקציה הראשונה שרצה בתוכניות. פונקציית ()setup פשוט נקראת מתוך ה-()main. אבל בעולם של מיקרובקרים לא תמיד ה-()main היא ההתחלה, הרי יש את ה-bootloader שרץ לפני הכל והוא יכול לקפוץ לכל נקודה בזכרון ולהמשיך משם. ה-()main עדיין יהיה קיים לפחות לצורכי הקומפילציה.

אז בואו נראה מה יש לנו בזכרון בכתובת הראשונה של ה-FLASH מייד אחרי ה-bootloader... אני מניח שזה האיזור אליו תתבצע הקפיצה. חיפשתי את הקוד של ה-bootloader לכרטיס XIAO, מצאתי קובץ INO שיכול לצרוב את ה-bootloader, אבל התוכן שלו רשום שם כבינארי, כך שאני לא יודע להגיד בוודאות לאן מתבצעת הקפיצה, אבל בואו נראה מה נמצא בכתובת 0x2000 שזו הכתובת הראשונה אחרי ה-bootloader... בשביל זה נציץ בקובץ MAP ונחפש את האיזור הרצוי:
קוד: בחר הכל
.text           0x0000000000002000     0x2a74
                0x0000000000002000                __text_start__ = .
*(.isr_vector)
.isr_vector    0x0000000000002000       0xb4 .pio/build/seeed_xiao/libFrameworkArduino.a(cortex_handlers.c.o)
                0x0000000000002000                exception_table


המממ... לפי השם נראה שיש שם טבלת פסיקות (ISR = Interrupt Service Routine). שם הטבלה הוא exception_table שמוגדר בקובץ cortex_handlers.c. מצאתי את הקובץ בספריות ההתקנה של PlatformIO, והנה הטבלה:
קוד: בחר הכל
/* Exception Table */
__attribute__ ((section(".isr_vector"))) const DeviceVectors exception_table =
{
  /* Configure Initial Stack Pointer, using linker-generated symbols */
  (void*) (&__StackTop),

  (void*) Reset_Handler,
  (void*) NMI_Handler,
  (void*) HardFault_Handler,
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) SVC_Handler,
  (void*) (0UL), /* Reserved */
  (void*) (0UL), /* Reserved */
  (void*) PendSV_Handler,
  (void*) SysTick_Handler,

  /* Configurable interrupts */
  (void*) PM_Handler,             /*  0 Power Manager */
  (void*) SYSCTRL_Handler,        /*  1 System Control */
  (void*) WDT_Handler,            /*  2 Watchdog Timer */
  (void*) RTC_Handler,            /*  3 Real-Time Counter */
  (void*) EIC_Handler,            /*  4 External Interrupt Controller */
  (void*) NVMCTRL_Handler,        /*  5 Non-Volatile Memory Controller */
  (void*) DMAC_Handler,           /*  6 Direct Memory Access Controller */
  (void*) USB_Handler,            /*  7 Universal Serial Bus */
  (void*) EVSYS_Handler,          /*  8 Event System Interface */
  (void*) SERCOM0_Handler,        /*  9 Serial Communication Interface 0 */
  (void*) SERCOM1_Handler,        /* 10 Serial Communication Interface 1 */
  (void*) SERCOM2_Handler,        /* 11 Serial Communication Interface 2 */
  (void*) SERCOM3_Handler,        /* 12 Serial Communication Interface 3 */
  (void*) SERCOM4_Handler,        /* 13 Serial Communication Interface 4 */
  (void*) SERCOM5_Handler,        /* 14 Serial Communication Interface 5 */
  (void*) TCC0_Handler,           /* 15 Timer Counter Control 0 */
  (void*) TCC1_Handler,           /* 16 Timer Counter Control 1 */
  (void*) TCC2_Handler,           /* 17 Timer Counter Control 2 */
  (void*) TC3_Handler,            /* 18 Basic Timer Counter 0 */
  (void*) TC4_Handler,            /* 19 Basic Timer Counter 1 */
  (void*) TC5_Handler,            /* 20 Basic Timer Counter 2 */
  (void*) TC6_Handler,            /* 21 Basic Timer Counter 3 */
  (void*) TC7_Handler,            /* 22 Basic Timer Counter 4 */
  (void*) ADC_Handler,            /* 23 Analog Digital Converter */
  (void*) AC_Handler,             /* 24 Analog Comparators */
  (void*) DAC_Handler,            /* 25 Digital Analog Converter */
  (void*) PTC_Handler,            /* 26 Peripheral Touch Controller */
  (void*) I2S_Handler,            /* 27 Inter-IC Sound Interface */
  (void*) (0UL),                  /* Reserved */
};


מה שמייד קופץ לעין זו ההגדרה שטבלה זו צריכה ללכת לאיזור ה-isr_vector.:
קוד: בחר הכל
__attribute__ ((section(".isr_vector")))


נראה שלאיזור זה שמור המקום הראשון בהגדרת איזור ה-FLASH:
קוד: בחר הכל
.text :
    {
        __text_start__ = .;

        KEEP(*(.isr_vector))
        *(.text*)

        KEEP(*(.init))
        KEEP(*(.fini))

        /* .ctors */
        *crtbegin.o(.ctors)
        *crtbegin?.o(.ctors)
        *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
        *(SORT(.ctors.*))
        *(.ctors)

        /* .dtors */
         *crtbegin.o(.dtors)
         *crtbegin?.o(.dtors)
         *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
         *(SORT(.dtors.*))
         *(.dtors)

        *(.rodata*)

        KEEP(*(.eh_frame*))
    } > FLASH


אוקי... אז זה לא ()main וגם לא פונקציה אחרת...
נראה שטבלת ה-exception_table כוללת כתובות של פונקציות שנקראות במקרים שונים, טיימרים, פסיקות חיצוניות, חיווי מרכיבי תקשורת טורית וכו', וגם כמה פונקציות מעניינות יותר: HardFault_Handler שכנראה נקראת כשמשהו ממש נהרס (נראה לי הגיוני להתלבש על הפונקציה הזו עם כל הניסויים שלי, רוב הסיכויים שמשהו ידפק בדרך וזו תהיה הדרך שלי לזהות את המצב), WDT_Handler כלב שמירה שהזכרתי בפוסט הקודם, שזה טוב, זה אומר שיש תמיכה חומרתית וצריך יהיה ללמוד איך להשתמש במנגנון זה, וגם Reset_Handler שלפי השם נראה שמטפלת ב-reset, עדיין לא יודע אם עושה reset או הפונקציה היא זו שנקראת אחרי ה-reset. בואו נראה:
קוד: בחר הכל
extern int main(void);

/* This is called on processor reset to initialize the device and call main() */
void Reset_Handler(void)
{
  uint32_t *pSrc, *pDest;

  /* Initialize the initialized data section */
  pSrc = &__etext;
  pDest = &__data_start__;

  if ((&__data_start__ != &__data_end__) && (pSrc != pDest)) {
    for ( ; pDest < &__data_end__; pDest++, pSrc++)
      *pDest = *pSrc;
  }

  /* Clear the zero section */
  if ((&__data_start__ != &__data_end__) && (pSrc != pDest)) {
    for (pDest = &__bss_start__; pDest < &__bss_end__; pDest++)
      *pDest = 0;
  }

#if defined(__FPU_USED) && defined(__SAMD51__)
/* Enable FPU */
SCB->CPACR |= (0xFu << 20);
__DSB();
__ISB();
#endif

  SystemInit();

  main();

  while (1)
    ;
}


אהא! מעתיקים משהו עם הלולאות בתחילת הפונקציה, קריאה ל-()SystemInit לאתחול של המערכת (בעיקר תדר שעונים וכו') ואז... קריאה ל-()main ומשם כבר מגיעים ל-()setup ו-()loop המוכרים.

המעבד צריך לדעת איפה ממוקמת טבלת ה-ISR, כך שמאוד יכול להיות ש-bootloader הוא זה שמאתחל את הכתובת של הטבלה בריגיסטרים של המעבד ויכול להיות שאחרי זה קופץ לפונקציה שהכתובת שלה שרשומה ב-0x2004, שזה הפונקציה Reset_Handler. או שעושה איזה שהוא Soft-Reset למעבד, כך שהוא קופץ לשם כחלק מתהליך העליה. חיפשתי די הרבה במפרט של SAMD21, אבל לא הצלחתי למצוא הזכור לתהליך כזה. מצאתי את המדריך הזה שמזכיר את "Vector Table Offset Register (VTOR)" שעושה בדיוק את מה שאני מחפש, אבל אני לא מצליח למצוא קוד שמטפל בשינוי הכתובות. ושוב, יכול להיות שזה נעשה ב-bootloader שאין את הקוד שלו.
כאן מצאתי את הכתובת של הרגיסטר הזה, אולי פשוט צריך לנסות לקרוא את הערך שלו ולראות מה יש שם...

אז מה זה אומר מבחינתי? 
כתבתי בפוסט הקודם שבגלל שיש הרבה קוד שמתקמפל בנוסף לקוד שלי, הכיוון הנכון יהיה כנראה שכל הקוד ילך לאיזור זכרון שצריך יהיה לצרוב בעדכון תוכנה, חוץ מקוד מיוחד שצריך להישאר באיזור ההתחלתי של ה-FLASH. טבלת ה-exception_table מגיעה כחלק מסביבת הקומפילציה של כרטיס ה-XIAO, אז צריך לדאוג לעדכן גם אותה במקרה שהיא תשתנה (זה לא קוד שבשליטתי). אם אכן יש רגיסטר שמאפשר לציין את מיקום טבלת ה-ISR, אז קוד האתחול שישאר באיזור שלא מעודכן צריך יהיה להורות למעבד איפה נמצאת הטבלה האמיתית. וצריך גם לקוות שאין שום קוד מוחבא איפה שהוא שניגש לכתובת 0x2000 כדי לשנות את הטבלה במהלך הריצה..
אם בסופו של דבר לא אמצא את הרגיסטר הזה, אז בנוסף לקוד המיוחד שלא מוחלף צריך גם לממש את טבלת ה-exception_table משלי שתשב בכתובת המקורית, והמימוש של כל הפונקציות בטבלה זו יהיה לקפוץ לפונקציות האמיתיות שצריכות לטפל בפסיקות.

הודעות נוספות:

  • בלוג איזורי זכרון ב-Arduino

    איזורי זכרון ב-Arduino

    2022-08-20 17:37:13

    כבר הרבה מאוד זמן (שנים) רץ אצלי הרעיון לבנות מערכת בית חכם DIY ממש מאפס. הרעיון מלוטש מאוד, הדבר העיקרי שחסר כדי לממש את הרעיון הוא הזמן...
    אז היום החלטתי להשקיע בזה כמה שעות כדי לראות כמה זה יכול להיות קשה לייצר boot כפול בכרטיס ארדואינו.

  • בלוג איפה ה-ESP32?

    איפה ה-ESP32?

    2022-07-17 20:29:46

    אז מה הסיפור של כרטיסי פיתוח ESP32 שלא כל כך קל למצוא בארץ?
    השאלה בעצם רלוונטית לכל המוצרים עם יכולות תקשורת אלחוטית, ZigBee, WiFi, LoRa, Bluetooth והמודולים הפשוטים שפעם היו בשוק.

  • בלוג השפעות הקורונה על התעשיה

    השפעות הקורונה על התעשיה

    2021-11-14 10:47:51

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

  • בלוג סיכום 2020

    סיכום 2020

    2021-01-08 13:23:52

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

  • בלוג ברוכים הבאים לעולם המילימטרי של goBILDA!

    ברוכים הבאים לעולם המילימטרי של goBILDA!

    2020-08-08 20:33:19

    אנו מחליפים את היצע של רכיבי המכניקה מסדרת Actobotics האינצ'ית לסדרת goBILDA המילימטרית.
    כל המוצרים מסדרת Actobotics מעודכנים למכירה עד גמר המלאי עם הנחות של 20-25%.