14.1.1.4. Depuração do firmware

A depuração em hardware significa parar o processador, definir pontos de interrupção no código-fonte C, executar passo a passo e inspecionar variáveis, memória, registos e periféricos – a partir do VS Code. Para isso são necessários três elementos: uma compilação de depuração, uma sonda de depuração SWD (um Segger J-Link) e a extensão Cortex-Debug a conduzir o arm-none-eabi-gdb contra um servidor GDB do J-Link.

14.1.1.4.1. Compilar para depuração

Recompile sempre o alvo com DEBUG=1

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

Uma imagem de lançamento (DEBUG=0) é compilada com -O2; no depurador verá <optimized out> para muitas variáveis, as funções inline colapsar-se-ão nos seus chamadores e a execução passo a passo salta de forma imprevisível. O DEBUG=1 compila com -Og -ggdb3, que é depurável mas ainda arranca na câmara. O ELF que se aponta ao depurador é:

build/<TARGET>/bin/firmware.elf

(Para o Alif AE3, depure build/OPENMV_AE3/bin/firmware_M55_HP.elf – o núcleo de alto desempenho.)

14.1.1.4.3. Configuração do Cortex-Debug no VS Code

Crie .vscode/launch.json no repositório. O caso mais simples – VS Code, o J-Link e a compilação estão todos na mesma máquina Linux / macOS – utiliza servertype: "jlink", o que faz o Cortex-Debug iniciar ele próprio um servidor GDB do 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"
    }
  ]
}

Altere executable e device para a sua placa (consulte a tabela acima). Prima F5 para compilar, flashar e executar até main e parar aí.

Dica

Para recompilar automaticamente sempre que inicia a depuração, adicione uma tarefa de compilação a .vscode/tasks.json e referencie-a na configuração de lançamento com "preLaunchTask". Por exemplo, uma tarefa que execute make -j$(nproc) TARGET=OPENMV4 DEBUG=1, com o nome "build-firmware", mais "preLaunchTask": "build-firmware" na configuração acima, para que F5 recompile, flashe e inicie o depurador num único passo.

Aviso

O Cortex-Debug precisa do arm-none-eabi-gdb. É fornecido no SDK em ~/openmv-sdk-<version>/gcc/bin, mas não está no PATH por defeito, pelo que a depuração falha com «GDB executable “arm-none-eabi-gdb” was not found». Corrija isto definindo armToolchainPath / gdbPath conforme mostrado acima, ou adicionando ~/openmv-sdk-<version>/gcc/bin ao seu PATH (printenv PATH deverá então listá-lo).

14.1.1.4.4. Vista de registos de periféricos (SVD)

Aponte o Cortex-Debug para um ficheiro SVD CMSIS para obter uma vista descodificada dos registos de periféricos (temporizadores, DMA, a interface da câmara, etc.) por nome e campo de bits:

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

Para STM32 e MIMXRT, obtenha o SVD dos packs CMSIS ST / NXP ou do registo SVD do Cortex-Debug. Os SVDs do Alif estão incluídos no repositório do firmware em lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (utilize o ..._CM55_HP_View.svd para o núcleo HP do AE3).

14.1.1.4.6. Depuração em linha de comandos com gdbrunner

Configurar uma sessão GDB contra um alvo embebido manualmente é um processo de cinco etapas: iniciar o servidor GDB do J-Link / ST-Link numa janela com os flags corretos de dispositivo, porta e interface; aguardar que imprima Waiting for GDB connection; executar arm-none-eabi-gdb numa segunda janela; digitar target remote localhost:<port>; apontar o gdb ao ELF. Quando a sessão gdb termina, lembrar de terminar a janela do servidor. O gdbrunner é um pequeno CLI que condensa tudo isto num único comando em primeiro plano. É fornecido no ambiente Python do SDK do OpenMV, pelo que não há nada a instalar; o ponto de entrada habitual é o alvo make debug do repositório do firmware:

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

Isto executa o gdbrunner com os argumentos do depurador da configuração da placa – o nome de dispositivo J-Link e, quando necessário, o flash loader externo ST-Link – com o arm-none-eabi-gdb do SDK já no PATH. O backend padrão é J-Link; make DEBUGGER=STLINK debug funciona com uma sonda ST-Link.

O gdbrunner também pode ser invocado diretamente (fora do 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

O primeiro argumento posicional escolhe o backend do servidor (jlink, stlink, qemu); os restantes são repassados a esse backend, com valores padrão que funcionam para as câmaras OpenMV. gdbrunner --help lista a lista completa de flags por backend; a tabela de argumentos de cada backend é controlada por JSON (src/gdbrunner/backends.json), pelo que adicionar um novo servidor é uma edição de configuração e não de código.

O que o gdbrunner faz para trabalho em linha de comandos:

  • Um processo, ciclo de vida limpo. O servidor inicia, o gdb liga-se quando a porta está aberta, o servidor é terminado de forma limpa quando o gdb sai. Sem JLinkGDBServer órfão a sobreviver à sessão, sem dois terminais para gerir.

  • Descoberta automática do STM32CubeProgrammer. O backend stlink procura nas localizações de instalação habituais (~/STM32CubeProgrammer/, /opt/st/, a árvore de plugins do STM32CubeIDE) as ferramentas do STM32CubeProgrammer, pelo que o longo caminho --cube-prog não precisa de ser digitado de cada vez. O SDK inclui a sua própria cópia em ~/openmv-sdk-<version>/stcubeprog/bin – aponte --cube-prog para lá se não existir uma instalação do sistema.

  • Honra o gdbinit do projeto. Um .gdbinit no diretório atual é carregado com -ix – substituindo o ~/.gdbinit global do utilizador – para que scripts gdb por projeto (pretty-printers, macros específicas de placa, conjuntos de pontos de interrupção) sejam aplicados simplesmente por estar presentes no diretório de trabalho. O make debug executa a partir da raiz do repositório, pelo que um .gdbinit aí aplica-se.

  • Execução de teste. --dryrun imprime o comando do servidor sem o executar, útil para adaptar a invocação a um script de wrapper, copiá-lo para uma configuração de lançador de IDE ou simplesmente verificar que argumentos o gdbrunner está a compor.

  • Saída do servidor visível. --show-output mantém o stdout / stderr do servidor visível. O padrão suprime-o (para que a interface do gdb se mantenha limpa); inverta o flag quando o problema está no próprio servidor.

  • Backend QEMU. qemu-system-arm depura uma compilação de firmware sem nenhuma placa ligada. O alvo MPS2_AN500 seleciona este backend na sua configuração de placa, pelo que make TARGET=MPS2_AN500 DEBUG=1 debug compila para a máquina mps2-an500 do QEMU e executa passo a passo o código independente de plataforma – tudo o que não toca em periféricos específicos da câmara – em voo. (qemu-system-arm é uma instalação do sistema anfitrião, não faz parte do SDK.)

Para execução passo a passo ao nível do código-fonte com indicadores de ponto de interrupção e vista de registos de periféricos, a configuração do Cortex-Debug no VS Code descrita acima é a melhor ferramenta; o gdbrunner é a certa para tudo o que vive na linha de comandos.

14.1.1.4.7. Utilizar o depurador

Uma vez que uma sessão esteja a decorrer (o processador parado em main):

  • Pontos de interrupção – clique na margem junto a uma linha C, ou na Consola de Depuração break <file>:<line> / break <function>. Os núcleos Cortex-M têm um número reduzido de hardware breakpoint comparators (tipicamente 6–8 no M7 / H7, 8 no M55). Exceder esse número em código em flash falha silenciosamente – mantenha o número de pontos de interrupção ativos modesto.

  • Execução passo a passoF10 passo por cima (next), F11 passo para dentro (step), Shift+F11 passo para fora (finish), F5 continuar. A execução passo a passo ao nível de instrução é stepi / nexti na Consola de Depuração.

  • Variáveis / vigiar / pilha de chamadas – os painéis Variables e Call Stack mostram variáveis locais e o backtrace; adicione expressões a Watch. Passe o rato sobre uma variável no código-fonte para ver o seu valor. Qualquer coisa que mostre <optimized out> significa que não está numa compilação DEBUG=1.

  • Watchpoints (pontos de interrupção de dados)watch <expr> para quando uma variável é escrita, rwatch na leitura, awatch em ambos. A unidade DWT do Cortex-M suporta ~4 watchpoints de hardware – inestimável para detetar quem corrompeu uma variável.

  • Registos e periféricos – a vista Cortex Registers mostra os registos do núcleo; com svdFile definido, a vista Peripherals descodifica todos os registos e campos de bits de periféricos (DMA, temporizadores, a interface câmara / CSI, XSPI, etc.) – a forma mais rápida de ver porque é que um driver está a funcionar mal.

  • Memória – utilize o visualizador de memória do Cortex-Debug ou o x/ do gdb para inspecionar diretamente buffers de fotograma, buffers DMA e estruturas.

  • printf sem parar (SWO/RTT) – para problemas sensíveis ao tempo, o RTT ou SWO da Segger fornece printf com sobrecarga quase nula enquanto o alvo está em execução. Compile com DEBUG_PRINTF=1 e adicione rttConfig (RTT) ou swoConfig (SWO, precisa do clock do núcleo) do Cortex-Debug. Esta é a ferramenta certa quando um ponto de interrupção alteraria o tempo que está a tentar observar.

  • DesligarStop numa sessão launch para o alvo; Disconnect numa sessão attach deixa a câmara a correr. Desligue e ligue a câmara novamente para a devolver ao funcionamento normal.

14.1.1.4.8. Armadilhas de depuração

  • Variáveis otimizadas para fora. Tudo mostra <optimized out> – compilou com DEBUG=0. Recompile com DEBUG=1.

  • «GDB executable not found» – o gcc/bin do SDK não está no PATH; defina armToolchainPath / gdbPath.

  • «Cannot connect» / mapa de memória errado – nome device errado ou em falta; utilize a string exata da tabela.

  • Pontos de interrupção silenciosamente não ativados – demasiados pontos de interrupção de hardware em código residente em flash; reduza-os.

  • Caminhos de código-fonte não coincidem (ELF compilado com Docker) – compile com o alvo build-firmware-dev do Docker (mesmo caminho absoluto dentro e fora do contentor) ou defina set substitute-path no gdb.