14.1.1.4. ניפוי באגים של הקושחה

ניפוי באגים על החומרה (on-hardware debugging) פירושו עצירת המעבד, הצבת נקודות עצירה (breakpoints) בקוד המקור ב-C, צעידה צעד-אחר-צעד ובחינה של משתנים, זיכרון, אוגרים (registers) והתקנים היקפיים – והכול מתוך VS Code. לשם כך נדרשים שלושה דברים: בנייה לניפוי באגים (debug build), בדיקת ניפוי SWD (Segger J-Link), והרחבת Cortex-Debug המפעילה את arm-none-eabi-gdb מול שרת GDB של J-Link.

14.1.1.4.1. בנייה לניפוי באגים

תמיד בנו מחדש את המטרה עם DEBUG=1

make -j$(nproc) TARGET=<TARGET> DEBUG=1

תמונת שחרור (DEBUG=0) מקומפלת עם -O2; במנפה הבאגים תראו <optimized out> עבור משתנים רבים, פונקציות inline מתמזגות לתוך הקוראים שלהן, והצעידה קופצת באופן בלתי צפוי. בנייה עם DEBUG=1 משתמשת ב--Og -ggdb3, שהיא ניתנת לניפוי באגים ועדיין עולה (boot) על המצלמה. קובץ ה-ELF שאליו אתם מפנים את מנפה הבאגים הוא:

build/<TARGET>/bin/firmware.elf

(עבור Alif AE3, נפו באגים של build/OPENMV_AE3/bin/firmware_M55_HP.elf – הליבה בעלת הביצועים הגבוהים.)

14.1.1.4.3. הגדרת Cortex-Debug ב-VS Code

צרו .vscode/launch.json במאגר. המקרה הפשוט ביותר – VS Code, ה-J-Link והבנייה כולם על אותה מכונת Linux / macOS – משתמש ב-servertype: "jlink", המורה ל-Cortex-Debug להפעיל בעצמו שרת GDB של J-Link:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "OpenMV J-Link",
      "type": "cortex-debug",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf",
      "servertype": "jlink",
      "device": "STM32H743VI",
      "interface": "swd",
      "runToEntryPoint": "main",
      "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin",
      "gdbPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin/arm-none-eabi-gdb"
    }
  ]
}

שנו את executable ואת device עבור הלוח שלכם (ראו את הטבלה למעלה). הקישו F5 כדי לבנות-לצרוב-ולהריץ עד main ולעצור שם.

טיפ

כדי לבנות מחדש אוטומטית בכל פעם שאתם מתחילים ניפוי באגים, הוסיפו משימת בנייה ל-.vscode/tasks.json והפנו אליה מתצורת ההפעלה באמצעות "preLaunchTask". לדוגמה, משימה המריצה make -j$(nproc) TARGET=OPENMV4 DEBUG=1, בשם "build-firmware", בתוספת "preLaunchTask": "build-firmware" בתצורה שלמעלה, כך ש-F5 בונה מחדש, צורב ומפעיל את מנפה הבאגים בצעד אחד.

אזהרה

Cortex-Debug זקוק ל-arm-none-eabi-gdb. הוא מגיע ב-SDK בנתיב ~/openmv-sdk-<version>/gcc/bin אך אינו נמצא ב-PATH כברירת מחדל, ולכן ניפוי הבאגים נכשל עם ההודעה ”GDB executable »arm-none-eabi-gdb« was not found“. תקנו זאת בין על ידי הגדרת armToolchainPath / gdbPath כפי שמוצג למעלה, ובין על ידי הוספת ~/openmv-sdk-<version>/gcc/bin ל-PATH שלכם (printenv PATH אמור אז להציג אותו).

14.1.1.4.4. תצוגת אוגרי התקנים היקפיים (SVD)

הפנו את Cortex-Debug לקובץ SVD של CMSIS כדי לקבל תצוגה מפוענחת של אוגרי ההתקנים ההיקפיים (טיימרים, DMA, ממשק המצלמה וכו«) לפי שם ולפי שדה ביטים:

"svdFile": "/path/to/STM32H743.svd"

עבור STM32 ו-MIMXRT, השיגו את ה-SVD מחבילות ה-CMSIS של ST / NXP או מרשם ה-SVD של Cortex-Debug. ה-SVD של Alif מצורפים במאגר הקושחה בנתיב lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (השתמשו ב-..._CM55_HP_View.svd עבור ליבת ה-HP של AE3).

14.1.1.4.6. ניפוי באגים משורת הפקודה עם gdbrunner

הקמת מושב GDB מול מטרה משובצת ביד היא ריקוד בן חמישה שלבים: הפעילו את שרת ה-GDB של J-Link / ST-Link בחלון אחד עם דגלי ההתקן, היציאה והממשק הנכונים; המתינו עד שהוא ידפיס Waiting for GDB connection; הריצו את arm-none-eabi-gdb בחלון שני; הקלידו target remote localhost:<port>; הפנו את gdb ל-ELF. כשמושב ה-gdb מסתיים, זכרו לסגור את חלון השרת. gdbrunner הוא כלי CLI קטן שמכווץ את כל זה לפקודה אחת בחזית. הוא מגיע בסביבת ה-Python של OpenMV SDK, כך שאין מה להתקין; נקודת הכניסה הרגילה היא מטרת ה-make debug של מאגר הקושחה:

make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug

פעולה זו מריצה את gdbrunner עם ארגומנטי מנפה הבאגים מתצורת הלוח – שם התקן ה-J-Link, ובמקום שבו נדרש, טוען הפלאש החיצוני של ST-Link – כאשר ה-arm-none-eabi-gdb של ה-SDK כבר נמצא ב-PATH. ברירת המחדל של ה-backend היא J-Link; make DEBUGGER=STLINK debug עובד עם בדיקת ST-Link במקום זאת.

ניתן גם להפעיל את gdbrunner ישירות (מחוץ ל-SDK, pip install gdbrunner):

gdbrunner jlink --device STM32H743VI build/OPENMV4/bin/firmware.elf
gdbrunner stlink build/OPENMV4/bin/firmware.elf
gdbrunner qemu --machine mps2-an500 build/MPS2_AN500/bin/firmware.elf

הארגומנט המיקומי הראשון בוחר את ה-backend של השרת (jlink, stlink, qemu); השאר מועברים ל-backend זה, עם ברירות מחדל העובדות עבור מצלמות OpenMV. gdbrunner --help מציג את רשימת הדגלים המלאה לכל backend; טבלת הארגומנטים של כל backend מונעת JSON (src/gdbrunner/backends.json), כך שהוספת שרת חדש היא עריכת תצורה ולא עריכת קוד.

מה gdbrunner עושה עבור עבודת שורת פקודה:

  • תהליך אחד, מחזור חיים נקי. השרת מתחיל, gdb מתחבר כשהיציאה פתוחה, השרת נסגר בצורה נקייה כש-gdb יוצא. אין JLinkGDBServer יתום ששורד את המושב, אין שני טרמינלים לנהל.

  • גילוי אוטומטי של STM32CubeProgrammer. ה-backend של stlink מחפש במיקומי ההתקנה הרגילים (~/STM32CubeProgrammer/, /opt/st/, עץ התוסף של STM32CubeIDE) את כלי STM32CubeProgrammer, כך שאין צורך להקליד בכל פעם את הנתיב הארוך --cube-prog. ה-SDK מצרף עותק משלו בנתיב ~/openmv-sdk-<version>/stcubeprog/bin – הפנו את --cube-prog לשם אם אין התקנת מערכת קיימת.

  • כיבוד gdbinit לכל פרויקט. קובץ .gdbinit בספרייה הנוכחית נטען עם -ix – ודורס את ה-~/.gdbinit הכלל-משתמשי – כך ש-scripting של gdb לכל פרויקט (pretty-printers, מאקרו ספציפי ללוח, קבוצות נקודות עצירה) נכנס בעצם נוכחותו בספריית העבודה. make debug רץ משורש המאגר, כך ש-.gdbinit שם חל.

  • הרצה יבשה. --dryrun מדפיס את פקודת השרת מבלי להריץ אותה, שימושי להתאמת הקריאה לסקריפט עוטף, להעתקתה לתצורת משגר ב-IDE, או פשוט לבדיקת אילו ארגומנטים gdbrunner מרכיב.

  • פלט השרת גלוי. --show-output משאיר את ה-stdout / stderr של השרת גלויים. ברירת המחדל מדכאת אותם (כדי שממשק המשתמש של gdb יישאר נקי); הפכו את הדגל כאשר השרת עצמו הוא זה שמתנהג לא כשורה.

  • Backend של QEMU. qemu-system-arm מנפה באגים בבנייה של קושחה ללא לוח מחובר. המטרה MPS2_AN500 בוחרת backend זה בתצורת הלוח שלה, כך ש-make TARGET=MPS2_AN500 DEBUG=1 debug בונה עבור מכונת mps2-an500 של QEMU וצועדת בקוד הבלתי תלוי בפלטפורמה – כל מה שאינו נוגע בהתקנים היקפיים ספציפיים למצלמה – בטיסה. (qemu-system-arm הוא התקנת מארח, לא חלק מה-SDK.)

לצעידה ברמת קוד המקור עם שולי נקודות עצירה ותצוגת אוגרי התקנים היקפיים, הגדרת Cortex-Debug ב-VS Code שלמעלה היא הכלי הטוב יותר; gdbrunner הוא הנכון לכל מה שחי בשורת הפקודה.

14.1.1.4.7. שימוש במנפה הבאגים

ברגע שמושב רץ (המעבד עצור ב-main):

  • נקודות עצירה – לחצו על השוליים ליד שורת C, או ב-Debug Console break <file>:<line> / break <function>. לליבות Cortex-M יש מספר קטן של משווי נקודות עצירה חומרתיים (בדרך כלל 6–8 ב-M7 / H7, 8 ב-M55). חריגה מכך על קוד בפלאש נכשלת בשקט – שמרו על מספר נקודות העצירה הפעילות מתון.

  • צעידהF10 צעד מעל (next), F11 צעד פנימה (step), Shift+F11 צעד החוצה (finish), F5 המשך. צעידה ברמת הוראה היא stepi / nexti ב-Debug Console.

  • משתנים / watch / מחסנית קריאות – חלוניות ה-Variables וה-Call Stack מציגות משתנים מקומיים ואת ה-backtrace; הוסיפו ביטויים ל-Watch. רחפו מעל משתנה בקוד המקור כדי לראות את ערכו. כל דבר שמציג <optimized out> משמעו שאינכם על בנייה של DEBUG=1.

  • Watchpoints (נקודות עצירה על נתונים)watch <expr> עוצר כאשר משתנה נכתב, rwatch בקריאה, awatch בכל אחד מהם. יחידת ה-DWT של Cortex-M תומכת ב-~4 watchpoints חומרתיים – שלא יסולא בפז לתפיסת מי פגם במשתנה.

  • אוגרים והתקנים היקפיים – תצוגת ה-Cortex Registers מציגה את אוגרי הליבה; כאשר svdFile מוגדר, תצוגת ה-Peripherals מפענחת כל אוגר התקן היקפי ושדה ביטים (DMA, טיימרים, ממשק המצלמה / CSI, XSPI וכו«) – הדרך המהירה ביותר לראות מדוע מנהל התקן מתנהג לא כשורה.

  • זיכרון – השתמשו במציג הזיכרון של Cortex-Debug או ב-x/ של gdb כדי לבחון ישירות חוצצי פריימים (framebuffers), חוצצי DMA ומבנים.

  • printf ללא עצירה (SWO/RTT) – עבור בעיות רגישות לתזמון, RTT או SWO של Segger מספקים printf בעלות כמעט-אפסית בעוד המטרה רצה. בנו עם DEBUG_PRINTF=1 והוסיפו את rttConfig (RTT) או swoConfig (SWO, זקוק לשעון הליבה) של Cortex-Debug. זה הכלי הנכון כאשר נקודת עצירה הייתה משנה את התזמון שאתם מנסים לצפות בו.

  • ניתוקStop במושב launch עוצר את המטרה; Disconnect במושב attach משאיר את המצלמה רצה. הפעילו מחזור מתח למצלמה כדי להחזירה לפעולה רגילה לאחר מכן.

14.1.1.4.8. מלכודות בניפוי באגים

  • משתנים שעברו אופטימיזציה החוצה. הכול מציג <optimized out> – בניתם עם DEBUG=0. בנו מחדש עם DEBUG=1.

  • ”GDB executable not found“ – ה-gcc/bin של ה-SDK אינו ב-PATH; הגדירו armToolchainPath / gdbPath.

  • ”Cannot connect“ / מפת זיכרון שגויה – שם device שגוי או חסר; השתמשו במחרוזת המדויקת מהטבלה.

  • נקודות עצירה שאינן נפגעות בשקט – יותר מדי נקודות עצירה חומרתיות על קוד השוכן בפלאש; הפחיתו אותן.

  • נתיבי מקור אינם תואמים (ELF שנבנה ב-Docker) – בנו עם מטרת ה-build-firmware-dev של Docker (אותו נתיב מוחלט בתוך המכל ומחוצה לו) או הגדירו set substitute-path ב-gdb.