Como usar o GDB (Gnu Debugger) e o OpenOCD para depuração de microcontroladores - a partir do terminal?
A maneira padrão (de baixo custo) de programar microcontroladores ARM é usar o Eclipse com uma cadeia de ferramentas complexa conectada a ele. Definitivamente, o Eclipse tem seus méritos, mas eu gostaria de me sentir independente desse IDE. Gostaria de descobrir o que acontece nos bastidores quando construo (compile - link - flash) meu software e quando executo uma sessão de depuração. Para obter um entendimento mais profundo, seria maravilhoso executar todo o procedimento na linha de comando.
Nota: Estou usando o Windows 10. de 64 bits, mas a maioria das coisas explicadas aqui também se aplica aos sistemas Linux. Por favor, abra todos os terminais de comando com direitos de administrador. Isso pode economizar muitos problemas.
1. Construindo o software
A primeira 'missão' é cumprida. Agora sou capaz de compilar e vincular meu software a um binário.bin
e um.elf
imagem através da linha de comando. A chave do sucesso foi descobrir onde o Eclipse coloca seus arquivos de criação para um projeto específico. Depois de saber onde eles estão, tudo o que você precisa fazer é abrir um terminal de comando e digite oGNU make
comando.
Você não precisa mais do Eclipse para isso! Especialmente se você puder ler (e entender) o makefile e ajustá-lo às suas necessidades quando o projeto avançar.
Observe que encontrei as ferramentas GNU (compilador, vinculador, utilitário de criação, GDB, ...) na seguinte pasta, depois de instalar o SW4STM32 (System Workbench for STM32):
C:\Ac6\SystemWorkbench\plugins\fr.ac6.mcu.externaltools.arm-none.win32_1.7.0.201602121829\tools\compiler\
Em seguida, criei uma nova pasta no meu disco rígido e copiei todas essas ferramentas GNU nele:
C:\Apps\AC6GCC
|-> arm-none-eabi
|-> bin
'-> lib
E adiciono essas entradas à "variável do caminho do ambiente":
- C:\Apps\AC6GCC\bin
- C:\Apps\AC6GCC\lib\gcc\arm-none-eabi\5.2.1
Huray, agora eu tenho todas as ferramentas GNU em funcionamento no meu sistema! Eu coloquei o seguintebuild.bat
na mesma pasta que o arquivomakefile
:
@echo off
echo.
echo."--------------------------------"
echo."- BUILD -"
echo."--------------------------------"
echo.
make -j8 -f makefile all
echo.
A execução deste arquivo bat deve fazer o trabalho! Se tudo correr bem, você recebe um.bin
e um.elf
arquivo binário como resultado da compilação.
2. Piscando e depurando o firmware
O passo seguinte natural é atualizar o firmware para o chip e iniciar uma sessão de depuração. No Eclipse, é apenas um 'clique em um botão' - pelo menos se o Eclipse estiver configurado corretamente para o seu microcontrolador. Mas o que acontece nos bastidores? Eu li (parte de) a Dissertação de Mestrado de Dominic Rath - desenvolvedor do OpenOCD. Você pode encontrá-lo aqui:http://openocd.net/ . Isto é o que eu aprendi:
O Eclipse inicia o software OpenOCD quando você clica no ícone 'debug'. O Eclipse também fornece alguns arquivos de configuração para o OpenOCD - como o OpenOCD sabe como se conectar ao seu microcontrolador. 'Como conectar' não é uma coisa trivial. O OpenOCD precisa encontrar o driver USB adequado para conectar ao adaptador JTAG (por exemplo, STLink). O adaptador JTAG e o driver USB geralmente são fornecidos pelo fabricante do seu chip (por exemplo, STMicroelectronics). O Eclipse também entrega um arquivo de configuração ao OpenOCD que descreve as especificações do microcontrolador. Quando o OpenOCD souber tudo isso, ele poderá estabelecer uma conexão JTAG confiável com o dispositivo de destino.
O OpenOCD inicia dois servidores. O primeiro é um servidor Telnet na porta TCP 4444. Ele fornece acesso ao OpenOCD CLI (Command Line Interface). Um cliente Telnet pode conectar e enviar comandos para o OpenOCD. Esses comandos podem ser um simples 'stop', 'run', 'set breakpoint', ...
Esses comandos podem ser suficientes para depurar seu microcontrolador, mas muitas pessoas já estavam familiarizadas com o Gnu Debugger (GDB). É por isso que o OpenOCD também inicia um servidor GDB na porta TCP 3333. Um cliente GDB pode se conectar a essa porta e começar a depurar o microcontrolador!
O Gnu Debugger é um software de linha de comando. Muitas pessoas preferem uma interface visual. É exatamente isso que o Eclipse faz. O Eclipse inicia um cliente GDB que se conecta ao OpenOCD - mas tudo está oculto para o usuário. O Eclipse fornece uma interface gráfica que interage com o cliente GDB nos bastidores.
Eu fiz uma figura para explicar todas essas coisas:
>> Iniciando o OpenOCD
Consegui iniciar o OpenOCD a partir da linha de comando. Eu vou explicar como
Primeiro, verifique se o seu programador STLink-V2 JTAG está instalado corretamente. Você pode testar a instalação com a "ferramenta STLink Utility" da STMicroelectronics. Tem uma interface gráfica agradável e você simplesmente clica no botão conectar.Abra um terminal de comando e inicie o OpenOCD. Você precisará fornecer ao OpenOCD alguns arquivos de configuração, para que ele saiba onde procurar seu microcontrolador. Normalmente, você precisa fornecer um arquivo de configuração que descreve o programador JTAG e um arquivo de configuração que define seu microcontrolador. Passe esses arquivos para o OpenOCD com o-f
argumento na linha de comando. Você também precisará conceder ao OpenOCD acesso aoscripts
pasta, passando-o com o-s
argumento. É assim que inicio o OpenOCD no meu computador com a linha de comando:
> "C:\Apps\OpenOCD-0.9.0-Win32\bin\openocd" -f "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts\interface\stlink-v2.cfg" -f "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts\target\stm32f7x.cfg" -s "C:\Apps\OpenOCD-0.9.0-Win32\share\openocd\scripts"
Se você iniciou o OpenOCD corretamente (com os argumentos corretos), ele será iniciado com a seguinte mensagem:
Open On-Chip Debugger 0.9.0 (2015-08-15-12:41)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v24 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.231496
Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints
Info : accepting 'gdb' connection on tcp/3333
Info : flash size probed value 1024
Observe que a janela do seu terminal está bloqueada. Você não pode mais digitar comandos. Mas isso é normal. O OpenOCD está sendo executado em segundo plano e bloqueia o terminal. Agora você tem duas opções para interagir com o OpenOCD: você inicia uma sessão Telnet em outro terminal e faz logon na porta TCPlocalhost:4444
, para que você possa dar comandos ao OpenOCD e receber feedback. Ou você inicia uma sessão do cliente GDB e a conecta à porta TCPlocalhost:3333
.
>> Iniciando uma sessão Telnet para interagir com o OpenOCD
É assim que você inicia uma sessão Telnet para interagir com o programa OpenOCD em execução:
> dism /online /Enable-Feature /FeatureName:TelnetClient
> telnet 127.0.0.1 4444
Se funcionar bem, você receberá a seguinte mensagem no seu terminal:
Open On-Chip Debugger
> ..
E você está pronto para enviar comandos ao OpenOCD! Mas agora vou mudar para a sessão GDB, já que essa é a maneira mais conveniente de interagir com o OpenOCD.
>> Iniciando uma sessão do cliente GDB para interagir com o OpenOCD
Abra outra janela do terminal e digite o seguinte comando:
> "C:\Apps\AC6GCC\bin\arm-none-eabi-gdb.exe"
Este comando simplesmente inicia oarm-none-eabi-gdb.exe
Cliente GDB. Se tudo der certo, o GDB será iniciado com a seguinte mensagem:
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20151217-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)..
Agora conecte esse cliente GDB ao servidor GDB dentro do OpenOCD:
(gdb) target remote localhost:3333
Agora você está conectado ao OpenOCD! É bom saber: se você deseja usar um comando OpenOCD nativo (como faria em uma sessão Telnet), basta preceder o comando com a palavra-chavemonitor
. Dessa forma, o servidor GDB dentro do OpenOCD não manipulará o comando, mas passará para o deamon nativo do OpenOCD.
Então, agora é hora de redefinir o chip, apagá-lo e interrompê-lo:
(gdb) monitor reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
(gdb) monitor halt
(gdb) monitor flash erase_address 0x08000000 0x00100000
erased address 0x08000000 (length 1048576) in 8.899024s (115.069 KiB/s)
(gdb) monitor reset halt
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0xfffffffe msp: 0xfffffffc
(gdb) monitor halt
O chip está pronto para receber algumas instruções de nós. Primeiro, diremos ao chip que suas seções de flash de 0 a 7 (todas as seções de flash do meu chip de 1Mb) não devem ser protegidas:
(gdb) monitor flash protect 0 0 7 off
(gdb) monitor flash info 0
#0 : stm32f7x at 0x08000000, size 0x00100000, buswidth 0, chipwidth 0
# 0: 0x00000000 (0x8000 32kB) not protected
# 1: 0x00008000 (0x8000 32kB) not protected
# 2: 0x00010000 (0x8000 32kB) not protected
# 3: 0x00018000 (0x8000 32kB) not protected
# 4: 0x00020000 (0x20000 128kB) not protected
# 5: 0x00040000 (0x40000 256kB) not protected
# 6: 0x00080000 (0x40000 256kB) not protected
# 7: 0x000c0000 (0x40000 256kB) not protected
Em seguida, paro o chip novamente. Só pra ter certeza..
(gdb) monitor halt
Finalmente eu entrego o binário.elf
arquivo para GDB:
(gdb) file C:\\..\\myProgram.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from C:\..\myProgram.elf ...done.
Agora é o momento da verdade. Peço ao GDB para carregar esse binário no chip. Dedos cruzados:
(gdb) load
Loading section .isr_vector, size 0x1c8 lma 0x8000000
Loading section .text, size 0x39e0 lma 0x80001c8
Loading section .rodata, size 0x34 lma 0x8003ba8
Loading section .init_array, size 0x4 lma 0x8003bdc
Loading section .fini_array, size 0x4 lma 0x8003be0
Loading section .data, size 0x38 lma 0x8003be4
Error finishing flash operation
Infelizmente, não foi bem sucedido. Recebo a seguinte mensagem no OpenOCD:
Error: error waiting for target flash write algorithm
Error: error writing to flash at address 0x08000000 at offset 0x00000000
EDIT: problema de hardware corrigido.
Aparentemente, foi um problema de hardware. Eu nunca pensei que meu chip estivesse com defeito, pois o carregamento do binário no chip com a ferramenta STLink Utility funcionou sem problemas. Somente o OpenOCD estava reclamando e dando erros. Então, naturalmente, culpei o OpenOCD - e não o próprio chip. Veja minha resposta abaixo para mais detalhes.
EDIT: Maneira alternativa elegante de piscar o chip - usando makefile!
Como o problema foi resolvido, agora vou me concentrar em uma maneira alternativa de executar o flash e a depuração do chip. Eu acredito que isso é realmente interessante para a comunidade!
Você deve ter notado que eu usei os comandos cmd do Windows para executar todas as etapas necessárias. Isso pode ser automatizado em um arquivo em lotes. Mas há uma maneira mais elegante: automatizar tudo em um makefile! Mr./Mss. Othane sugeriu o seguinte makefile para seu Cortex-M? lasca. Suponho que o procedimento para um chip Cortex-M7 seja muito semelhante:
#################################################
# MAKEFILE FOR BUILDING THE BINARY #
# AND EVEN FLASHING THE CHIP! #
# Author: Othane #
#################################################
# setup compiler and flags for stm32f373 build
SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
CROSS_COMPILE ?= arm-none-eabi-
export CC = $(CROSS_COMPILE)gcc
export AS = $(CROSS_COMPILE)gcc -x assembler-with-cpp
export AR = $(CROSS_COMPILE)ar
export LD = $(CROSS_COMPILE)ld
export OD = $(CROSS_COMPILE)objdump
export BIN = $(CROSS_COMPILE)objcopy -O ihex
export SIZE = $(CROSS_COMPILE)size
export GDB = $(CROSS_COMPILE)gdb
MCU = cortex-m4
FPU = -mfloat-abi=hard -mfpu=fpv4-sp-d16 -D__FPU_USED=1 -D__FPU_PRESENT=1 -DARM_MATH_CM4
DEFS = -DUSE_STDPERIPH_DRIVER -DSTM32F37X -DRUN_FROM_FLASH=1 -DHSE_VALUE=8000000
OPT ?= -O0
MCFLAGS = -mthumb -mcpu=$(MCU) $(FPU)
export ASFLAGS = $(MCFLAGS) $(OPT) -g -gdwarf-2 $(ADEFS)
CPFLAGS += $(MCFLAGS) $(OPT) -gdwarf-2 -Wall -Wno-attributes -fverbose-asm
CPFLAGS += -ffunction-sections -fdata-sections $(DEFS)
export CPFLAGS
export CFLAGS += $(CPFLAGS)
export LDFLAGS = $(MCFLAGS) -nostartfiles -Wl,--cref,--gc-sections,--no-warn-mismatch $(LIBDIR)
HINCDIR += ./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/CMSIS/Include/ \
./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/CMSIS/Device/ST/STM32F37x/Include/ \
./STM32F37x_DSP_StdPeriph_Lib_V1.0.0/Libraries/STM32F37x_StdPeriph_Driver/inc/ \
./
export INCDIR = $(patsubst %,$(SELF_DIR)%,$(HINCDIR))
# openocd variables and targets
OPENOCD_PATH ?= /usr/local/share/openocd/
export OPENOCD_BIN = openocd
export OPENOCD_INTERFACE = $(OPENOCD_PATH)/scripts/interface/stlink-v2.cfg
export OPENOCD_TARGET = $(OPENOCD_PATH)/scripts/target/stm32f3x_stlink.cfg
OPENOCD_FLASH_CMDS = ''
OPENOCD_FLASH_CMDS += -c 'reset halt'
OPENOCD_FLASH_CMDS += -c 'sleep 10'
OPENOCD_FLASH_CMDS += -c 'stm32f1x unlock 0'
OPENOCD_FLASH_CMDS += -c 'flash write_image erase $(PRJ_FULL) 0 ihex'
OPENOCD_FLASH_CMDS += -c shutdown
export OPENOCD_FLASH_CMDS
OPENOCD_ERASE_CMDS = ''
OPENOCD_ERASE_CMDS += -c 'reset halt'
OPENOCD_ERASE_CMDS += -c 'sleep 10'
OPENOCD_ERASE_CMDS += -c 'sleep 10'
OPENOCD_ERASE_CMDS += -c 'stm32f1x mass_erase 0'
OPENOCD_ERASE_CMDS += -c shutdown
export OPENOCD_ERASE_CMDS
OPENOCD_RUN_CMDS = ''
OPENOCD_RUN_CMDS += -c 'reset halt'
OPENOCD_RUN_CMDS += -c 'sleep 10'
OPENOCD_RUN_CMDS += -c 'reset run'
OPENOCD_RUN_CMDS += -c 'sleep 10'
OPENOCD_RUN_CMDS += -c shutdown
export OPENOCD_RUN_CMDS
OPENOCD_DEBUG_CMDS = ''
OPENOCD_DEBUG_CMDS += -c 'halt'
OPENOCD_DEBUG_CMDS += -c 'sleep 10'
.flash:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_FLASH_CMDS)
.erase:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_ERASE_CMDS)
.run:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_RUN_CMDS)
.debug:
$(OPENOCD_BIN) -f $(OPENOCD_INTERFACE) -f $(OPENOCD_TARGET) -c init $(OPENOCD_DEBUG_CMDS)
Caro Sr. / Sr. Othane, você poderia explicar como usar esse makefile para as seguintes etapas:
Construa o binário a partir do código fontePiscar o chipEu sei algumas noções básicas sobre makefiles, mas seu makefile está realmente indo bem fundo. Você parece usar alguns recursos do utilitário GNU make. Por favor, dê-nos mais algumas explicações e eu concederei o bônus ;-)
------------------------------