14.1.1.4. Debugowanie oprogramowania układowego

Debugowanie na sprzęcie oznacza zatrzymywanie procesora, ustawianie punktów wstrzymania w kodzie źródłowym C, wykonywanie krokowe oraz inspekcję zmiennych, pamięci, rejestrów i urządzeń peryferyjnych – wszystko z poziomu VS Code. Wymaga to trzech rzeczy: kompilacji debugowej, sondy debugowej SWD (Segger J-Link) oraz rozszerzenia Cortex-Debug sterującego arm-none-eabi-gdb względem serwera GDB J-Link.

14.1.1.4.1. Kompilacja do debugowania

Zawsze przebudowuj cel z DEBUG=1

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

Obraz wydaniowy (DEBUG=0) jest kompilowany z -O2; w debuggerze zobaczysz <optimized out> dla wielu zmiennych, funkcje wstawione w miejsce wywołania zostają zwinięte do swoich wywołujących, a wykonywanie krokowe przeskakuje w nieprzewidywalny sposób. DEBUG=1 buduje z -Og -ggdb3, co umożliwia debugowanie, a jednocześnie pozwala na uruchomienie na kamerze. Plik ELF, na który wskazujesz debugger, to:

build/<TARGET>/bin/firmware.elf

(Dla Alif AE3 debuguj build/OPENMV_AE3/bin/firmware_M55_HP.elf – rdzeń wysokiej wydajności.)

14.1.1.4.3. Konfiguracja Cortex-Debug w VS Code

Utwórz .vscode/launch.json w repozytorium. Najprostszy przypadek – VS Code, J-Link i kompilacja znajdują się na tej samej maszynie z systemem Linux / macOS – używa servertype: "jlink", co sprawia, że Cortex-Debug samodzielnie uruchamia serwer 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"
    }
  ]
}

Zmień executable oraz device dla swojej płytki (zobacz tabelę powyżej). Naciśnij F5, aby skompilować, wgrać i uruchomić do main i zatrzymać się tam.

Wskazówka

Aby automatycznie przebudowywać przy każdym uruchomieniu debugowania, dodaj zadanie budowania do .vscode/tasks.json i odwołaj się do niego z konfiguracji uruchamiania za pomocą "preLaunchTask". Na przykład zadanie uruchamiające make -j$(nproc) TARGET=OPENMV4 DEBUG=1, nazwane "build-firmware", plus "preLaunchTask": "build-firmware" w powyższej konfiguracji, tak aby F5 przebudowywało, wgrywało i uruchamiało debugger w jednym kroku.

Ostrzeżenie

Cortex-Debug potrzebuje arm-none-eabi-gdb. Jest on dostarczany w SDK pod ścieżką ~/openmv-sdk-<version>/gcc/bin, ale domyślnie nie znajduje się w PATH, więc debugowanie kończy się niepowodzeniem z komunikatem „GDB executable «arm-none-eabi-gdb» was not found”. Napraw to, ustawiając armToolchainPath / gdbPath jak pokazano powyżej, albo dodając ~/openmv-sdk-<version>/gcc/bin do swojego PATH (printenv PATH powinno wtedy go wymieniać).

14.1.1.4.4. Widok rejestrów urządzeń peryferyjnych (SVD)

Wskaż Cortex-Debug plik SVD standardu CMSIS, aby uzyskać zdekodowany widok rejestrów urządzeń peryferyjnych (liczniki czasu (timery), DMA, interfejs kamery itp.) według nazwy i pola bitowego:

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

Dla STM32 i MIMXRT pobierz SVD z paczek CMSIS firmy ST / NXP lub z rejestru SVD Cortex-Debug. Pliki SVD dla Alif są dołączone w repozytorium oprogramowania układowego pod ścieżką lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (użyj ..._CM55_HP_View.svd dla rdzenia HP AE3).

14.1.1.4.6. Debugowanie z wiersza poleceń za pomocą gdbrunner

Ręczne skonfigurowanie sesji GDB względem celu wbudowanego to pięcioetapowy taniec: uruchom serwer GDB J-Link / ST-Link w jednym oknie z właściwym urządzeniem, portem i flagami interfejsu; poczekaj, aż wypisze Waiting for GDB connection; uruchom arm-none-eabi-gdb w drugim oknie; wpisz target remote localhost:<port>; wskaż gdb plik ELF. Gdy sesja gdb się kończy, pamiętaj o zamknięciu okna serwera. gdbrunner to małe narzędzie CLI, które zwija to wszystko do jednego polecenia pierwszoplanowego. Jest dostarczane w środowisku Python SDK OpenMV, więc nie trzeba nic instalować; zwykłym punktem wejścia jest cel make debug repozytorium oprogramowania układowego:

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

Uruchamia to gdbrunner z argumentami debuggera z konfiguracji płytki – nazwą urządzenia J-Link oraz, w razie potrzeby, zewnętrznym loaderem pamięci flash ST-Link – z arm-none-eabi-gdb z SDK już obecnym w PATH. Domyślnym zapleczem jest J-Link; make DEBUGGER=STLINK debug działa z sondą ST-Link.

gdbrunner można też wywołać bezpośrednio (poza 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

Pierwszy argument pozycyjny wybiera zaplecze serwera (jlink, stlink, qemu); reszta jest przekazywana do tego zaplecza, z wartościami domyślnymi działającymi dla kamer OpenMV. gdbrunner --help wymienia pełną listę flag dla każdego zaplecza; tabela argumentów każdego zaplecza jest sterowana przez JSON (src/gdbrunner/backends.json), więc dodanie nowego serwera to edycja konfiguracji, a nie kodu.

Co gdbrunner robi dla pracy z wiersza poleceń:

  • Jeden proces, czysty cykl życia. Serwer uruchamia się, gdb podłącza się, gdy port jest otwarty, a serwer jest czysto kończony, gdy gdb się zamyka. Żadnego osieroconego JLinkGDBServer przeżywającego sesję, żadnych dwóch terminali do zarządzania.

  • Automatyczne wykrywanie STM32CubeProgrammer. Zaplecze stlink przeszukuje typowe lokalizacje instalacji (~/STM32CubeProgrammer/, /opt/st/, drzewo wtyczek STM32CubeIDE) w poszukiwaniu narzędzi STM32CubeProgrammer, więc długiej ścieżki --cube-prog nie trzeba wpisywać za każdym razem. SDK dołącza własną kopię pod ścieżką ~/openmv-sdk-<version>/stcubeprog/bin – wskaż tam --cube-prog, jeśli nie istnieje instalacja systemowa.

  • Uwzględniony gdbinit dla projektu. Plik .gdbinit w bieżącym katalogu jest ładowany z -ix – nadpisując ogólnoużytkownikowy ~/.gdbinit – więc skrypty gdb dla projektu (pretty-printery, makra specyficzne dla płytki, zestawy punktów wstrzymania) trafiają do akcji po prostu przez obecność w katalogu roboczym. make debug działa z katalogu głównego repozytorium, więc .gdbinit tam umieszczony obowiązuje.

  • Przebieg próbny. --dryrun wypisuje polecenie serwera bez jego uruchamiania, przydatne przy dostosowywaniu wywołania do skryptu opakowującego, kopiowaniu go do konfiguracji uruchamiania IDE lub po prostu sprawdzaniu, jakie argumenty gdbrunner składa.

  • Widoczne wyjście serwera. --show-output utrzymuje widoczne stdout / stderr serwera. Domyślnie jest ono tłumione (aby interfejs gdb pozostał czysty); przełącz flagę, gdy to sam serwer się źle zachowuje.

  • Zaplecze QEMU. qemu-system-arm debuguje kompilację oprogramowania układowego bez podłączonej płytki. Cel MPS2_AN500 wybiera to zaplecze w swojej konfiguracji płytki, więc make TARGET=MPS2_AN500 DEBUG=1 debug buduje dla maszyny mps2-an500 QEMU i wykonuje krokowo kod niezależny od platformy – wszystko, co nie dotyka urządzeń peryferyjnych specyficznych dla kamery – w locie. (qemu-system-arm to instalacja hosta, a nie część SDK.)

Do wykonywania krokowego na poziomie kodu źródłowego z marginesami punktów wstrzymania i widokiem rejestrów urządzeń peryferyjnych lepszym narzędziem jest powyższa konfiguracja Cortex-Debug w VS Code; gdbrunner jest właściwy dla wszystkiego, co żyje w wierszu poleceń.

14.1.1.4.7. Korzystanie z debuggera

Gdy sesja jest uruchomiona (procesor zatrzymany w main):

  • Punkty wstrzymania – kliknij margines obok linii C lub w konsoli debugowania break <file>:<line> / break <function>. Rdzenie Cortex-M mają niewielką liczbę sprzętowych komparatorów punktów wstrzymania (zwykle 6–8 na M7 / H7, 8 na M55). Przekroczenie tej liczby dla kodu w pamięci flash kończy się cichym niepowodzeniem – utrzymuj umiarkowaną liczbę aktywnych punktów wstrzymania.

  • Wykonywanie krokoweF10 krok ponad (next), F11 krok do wnętrza (step), Shift+F11 krok na zewnątrz (finish), F5 kontynuuj. Wykonywanie krokowe na poziomie instrukcji to stepi / nexti w konsoli debugowania.

  • Zmienne / obserwacja / stos wywołań – panele Variables i Call Stack pokazują zmienne lokalne oraz ślad wywołań; dodawaj wyrażenia do Watch. Najedź na zmienną w kodzie źródłowym, aby zobaczyć jej wartość. Wszystko, co pokazuje <optimized out>, oznacza, że nie jesteś na kompilacji DEBUG=1.

  • Punkty obserwacji (punkty wstrzymania na danych)watch <expr> zatrzymuje przy zapisie zmiennej, rwatch przy odczycie, awatch przy obu. Jednostka DWT Cortex-M obsługuje ~4 sprzętowe punkty obserwacji – nieocenione przy wychwytywaniu, kto uszkodził zmienną.

  • Rejestry i urządzenia peryferyjne – widok Cortex Registers pokazuje rejestry rdzenia; z ustawionym svdFile widok Peripherals dekoduje każdy rejestr urządzenia peryferyjnego i pole bitowe (DMA, liczniki czasu (timery), interfejs kamery / CSI, XSPI itp.) – najszybszy sposób, by zobaczyć, dlaczego sterownik źle się zachowuje.

  • Pamięć – użyj przeglądarki pamięci Cortex-Debug lub gdb x/, aby bezpośrednio badać bufory ramki, bufory DMA i struktury.

  • printf bez zatrzymywania (SWO/RTT) – przy problemach wrażliwych na czas Segger RTT lub SWO zapewnia printf o niemal zerowym narzucie podczas działania celu. Buduj z DEBUG_PRINTF=1 i dodaj rttConfig (RTT) lub swoConfig (SWO, wymaga zegara rdzenia) Cortex-Debug. To właściwe narzędzie, gdy punkt wstrzymania zmieniłby czas, który próbujesz obserwować.

  • OdłączanieStop w sesji launch zatrzymuje cel; Disconnect w sesji attach pozostawia kamerę działającą. Następnie wyłącz i włącz zasilanie kamery, aby przywrócić jej normalne działanie.

14.1.1.4.8. Pułapki debugowania

  • Zmienne usunięte przez optymalizację. Wszystko pokazuje <optimized out> – zbudowałeś z DEBUG=0. Przebuduj z DEBUG=1.

  • „GDB executable not found”gcc/bin z SDK nie jest w PATH; ustaw armToolchainPath / gdbPath.

  • „Cannot connect” / błędna mapa pamięci – błędna lub brakująca nazwa device; użyj dokładnego ciągu z tabeli.

  • Punkty wstrzymania cicho nietrafiane – zbyt wiele sprzętowych punktów wstrzymania na kodzie rezydującym w pamięci flash; zmniejsz ich liczbę.

  • Ścieżki źródłowe nie pasują (ELF zbudowany w Dockerze) – buduj z celem Docker build-firmware-dev (ta sama ścieżka bezwzględna wewnątrz i na zewnątrz kontenera) lub ustaw w gdb set substitute-path.