Comandos Fundamentais do GDB no STM32 (Breakpoints, Step, Memória e Registradores)
Agora que o pipeline está montado — ELF com símbolos, OpenOCD rodando e GDB conectado — vamos entrar no uso real do GDB no contexto de um STM32 (Cortex-M). Aqui eu vou organizar de forma didática, mas com profundidade técnica, para que você consiga usar o GDB tanto em projetos simples baremetal quanto em sistemas com FreeRTOS.
2.1 Breakpoints: o ponto de controle do firmware
O breakpoint é o mecanismo mais básico e mais poderoso da depuração. Ele diz ao processador: “quando chegar aqui, pare”.
O comando mais simples é:
break main
Ou por arquivo e linha:
break main.c:42
Ou por função:
break HAL_GPIO_TogglePin
O GDB responde algo como:
Breakpoint 1 at 0x08001234: file main.c, line 42.
Em STM32, é importante entender que breakpoints podem ser:
- Software breakpoints (inserem instrução BKPT na flash/RAM)
- Hardware breakpoints (usam comparadores do CoreSight)
Em Cortex-M, o número de hardware breakpoints é limitado (normalmente 4 ou 6, dependendo do core). Se você colocar muitos breakpoints em código que está na Flash, pode atingir esse limite. O GDB decide automaticamente qual tipo usar, mas se você notar erro do tipo “cannot insert breakpoint”, provavelmente atingiu o limite de hardware.
Para listar breakpoints:
info breakpoints
Para desabilitar temporariamente:
disable 1
Para habilitar novamente:
enable 1
Para remover:
delete 1
Você também pode criar breakpoints condicionais:
break main.c:42 if counter == 100
Isso é extremamente útil em firmware com loops rápidos, onde parar toda vez tornaria o debug impraticável.
2.2 Execução controlada: continue, step, next, finish
Depois que o firmware para em um breakpoint, você precisa controlar o fluxo.
Para continuar até o próximo breakpoint:
continue
Para executar linha a linha, entrando em funções:
step
Para executar linha a linha, sem entrar em funções:
next
Em firmware embarcado, step é particularmente útil quando você quer entrar dentro de uma função HAL, ou dentro de uma função de driver que você escreveu.
Se você entrou demais e quer sair da função atual:
finish
Isso executa até retornar da função atual.
Se você quer executar apenas uma instrução assembly:
stepi
Ou pular uma instrução assembly:
nexti
Esse nível é essencial quando você está depurando problemas de stack corruption, instruções inválidas ou HardFault.
2.3 Inspeção de variáveis
Para imprimir o valor de uma variável:
print counter
Ou de forma abreviada:
p counter
Para imprimir em hexadecimal:
p/x counter
Para binário:
p/t counter
Para imprimir estrutura completa:
p myStruct
Se for um ponteiro:
p *ptr
Você pode observar automaticamente uma variável enquanto executa:
display counter
Para remover da lista automática:
undisplay 1
2.4 Watchpoints: parar quando algo mudar
Watchpoints são extremamente poderosos em STM32 quando você suspeita que alguma variável está sendo alterada indevidamente por ISR ou DMA.
Para parar quando uma variável for modificada:
watch counter
Para parar quando for lida:
rwatch counter
Para leitura ou escrita:
awatch counter
Internamente, isso usa comparadores de hardware do Cortex-M (DWT). O número é limitado. Em muitos STM32 você tem 2 ou 4 watchpoints.
Se o GDB disser que não conseguiu inserir watchpoint, provavelmente atingiu o limite.
2.5 Registradores do Cortex-M
Para listar todos os registradores:
info registers
Você verá algo como:
r0 r1 r2 r3 r4 r5 r6 r7
r8 r9 r10 r11 r12
sp lr pc xPSR
Registradores críticos no STM32:
sp(stack pointer)lr(link register)pc(program counter)xPSR(status register)
Para inspecionar um específico:
p $pc
Para modificar (cuidado extremo):
set $pc = 0x08001234
Isso pode ser útil para testes, mas é perigoso.
2.6 Leitura e escrita de memória
Em sistemas embarcados, muitas vezes você precisa olhar registradores periféricos diretamente.
Para examinar memória:
x/4xw 0x40021000
Significado:
4→ quatro unidadesx→ hexadecimalw→ word (32 bits)
Outros formatos:
b→ byteh→ halfword (16 bits)g→ 64 bits
Exemplo lendo GPIO:
x/1xw 0x48000014
Para escrever memória:
set {uint32_t}0x48000014 = 0x00000001
Isso é extremamente útil para forçar estados de registradores e validar hipóteses sem recompilar firmware.
2.7 Backtrace e stack
Quando ocorre um HardFault ou comportamento inesperado, o primeiro comando que você deve usar é:
backtrace
Ou abreviado:
bt
Isso mostra a pilha de chamadas:
#0 HardFault_Handler
#1 some_function
#2 main
Para navegar entre frames:
frame 1
Para listar código no frame atual:
list
Se o stack estiver corrompido, o backtrace pode parecer incoerente — isso já é um indicativo forte de overflow de stack ou ponteiro inválido.
2.8 Comandos monitor (OpenOCD)
Como estamos usando OpenOCD, alguns comandos são passados com monitor:
Reset e halt:
monitor reset halt
Apenas reset:
monitor reset
Ver status do target:
monitor targets
Ler estado do core:
monitor arm core_state
Se estiver usando FreeRTOS, é comum carregar script específico do OpenOCD para suportar awareness de threads.
2.9 Comandos de controle geral do GDB
Listar código:
list
Mostrar arquivo atual:
info source
Ver threads (se RTOS estiver habilitado):
info threads
Trocar de thread:
thread 2
Sair:
quit
Até aqui cobrimos:
- Breakpoints simples e condicionais
- Watchpoints
- Step em C e assembly
- Inspeção de variáveis
- Manipulação de memória
- Registradores Cortex-M
- Backtrace
- Integração com OpenOCD
- Debug de HardFault passo a passo
- Diagnóstico de Stack Overflow
- Depuração com FreeRTOS (tasks e contexto)
- Debug de código otimizado
- Uso de
.gdbinite automação