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.2. החומרה: J-Link מעל SWD¶
חברו Segger J-Link לפיני ה-SWD של המצלמה (SWDIO, SWCLK, GND, ו-VCC של המטרה לצורך התייחסות; המצלמה מקבלת מתח דרך USB כרגיל). J-Link EDU / Base / Pro – כולם עובדים. המקום שבו צצים פיני הניפוי שונה ממצלמה למצלמה – ללוחות רבים יש מחבר JTAG/SWD ייעודי, אחרים חושפים את SWD על מסרק ה-I/O או על משטחי בדיקה – לכן בדקו בתרשים הפינים ובסכמה של אותו לוח בתיעוד החומרה של OpenMV אילו פינים יש לחווט. התקינו את J-Link Software and Documentation Pack מ-segger.com על המכונה שאליה הבדיקה מחוברת פיזית. שמרו אותו מעודכן באופן סביר – תוכנת J-Link ישנה לא תכיר את שמות ההתקנים החדשים יותר (STM32N6, MIMXRT, Alif).
כל MCU זקוק לשם הdevice המדויק שלו ב-J-Link כדי שהבדיקה תטען את טוען הפלאש ומפת הזיכרון הנכונים:
מצלמה ( |
MCU |
|
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
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.5. Windows: גשר ה-J-Link בין WSL ל-Windows¶
WSL 2 אינו יכול לראות ישירות את התקן ה-USB של ה-J-Link, ולכן החלוקה היא: Windows משרת את הבדיקה (היכן שהיא מחוברת) ו-VS Code + gdb רצים ב-WSL ומגיעים אליה דרך TCP.
ב-Windows, התקינו את חבילת ה-J-Link של Segger וחברו את ה-J-Link ליציאת USB של Windows.
ב-Windows, הפעילו את J-Link Remote Server (הוא מגיע עם חבילת ה-J-Link): הפעילו אותו כשה-J-Link מחובר ולחצו OK. אשרו אותו דרך חומת האש של Windows כאשר תתבקשו. החלון מציג את כתובת ה-IP שעליה הוא משרת את הבדיקה – רשמו אותה.
ב-WSL, בנו עם
DEBUG=1וודאו ש-arm-none-eabi-gdbנגיש (הגדירוarmToolchainPathכמתואר למעלה).ב-WSL VS Code, השאירו את
servertype: "jlink"– שרת ה-GDB רץ ב-WSL ומגיע לבדיקה דרך ה-Remote Server – והוסיפוserverpath+ipAddress{ "name": "OpenMV J-Link (Windows host)", "type": "cortex-debug", "request": "launch", "cwd": "${workspaceFolder}", "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf", "servertype": "jlink", "serverpath": "/opt/SEGGER/JLink/JLinkGDBServer", "ipAddress": "192.168.x.x", "device": "STM32H743VI", "interface": "swd", "runToEntryPoint": "main", "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin" }
הגדירו את
ipAddressלכתובת שחלון ה-Remote Server מציג. זהו כל הגשר.
טיפ
חלופה לגשר שרת ה-GDB: usbipd-win. במקום להריץ שרת ב-Windows, אתם יכולים לחבר את התקן ה-USB של ה-J-Link ישירות לתוך WSL באמצעות usbipd-win. מתוך PowerShell של מנהל מערכת:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> הוא מזהה האפיק (bus ID) של ה-J-Link מתוך usbipd list.) הבדיקה מופיעה אז בתוך WSL, ואתם משתמשים בתצורת servertype: "jlink" הפשוטה של אותה מכונה מתוך הגדרת Cortex-Debug ב-VS Code ללא כתובת IP וללא שרת Windows נפרד. גשר שרת ה-GDB דורש פחות הגדרה לשימוש מזדמן; usbipd-win נוח יותר לפיתוח שגרתי.
טיפ
השתמשו ב-"request": "attach" כדי לנפות באגים בקושחה בעודה כבר רצה מבלי לאפס או לצרוב אותה מחדש – אידיאלי לתפיסת תקיעה בשטח. השתמשו ב-"request": "launch" כדי לאפס, לצרוב את ה-ELF ולהתחיל מחדש בנקודת ה-runToEntryPoint.
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.