Linux Kernel Debugging com JTAG
- por Sergio Prado
Por um bom tempo, durante o desenvolvimento do kernel Linux, os desenvolvedores sentiram falta de uma boa e menos intrusiva interface de debugging. Foi só na versão 2.6.26 que o KGDB foi aprovado e entrou no mainline.
A ausência de um mecanismo de debugging é ainda mais sentida quando pensamos no desenvolvimento embedded, principalmente no trabalho de desenvolvimento de porte do bootloader ou do kernel para outras plataformas. Para quem já desenvolveu este tipo de trabalho de “board bring-up”, sabe que é onde mora a diversão! :)
E é aí que entra a interface JTAG…
JTAG
A interface JTAG (Joint Test Action Group) foi inicialmente desenvolvida como um mecanismo para testar placas após sua fabricação. Com o tempo, ela se tornou uma ferramenta importante para depurar sistemas embarcados. Com uma conexão JTAG, temos acesso a modulos de debug integrados ao núcleo da CPU, onde podemos colocá-la em halt, inspecionar registradores, memória, colocar breakpoints, etc.
Mas para fazer nossa máquina de desenvolvimento se comunicar com uma interface JTAG precisamos de alguns componentes, incluindo:
- Hardware com suporte à interface JTAG.
- Conversor/adaptador JTAG.
- Software de interface entre o adaptador JTAG e o debugger (OpenOCD).
- Debugger (GDB).
Que tal estudarmos estes componentes em mais detalhes?
HARDWARE
Nos meus testes utilizarei a Beagleboard-xM, que provê uma interface JTAG através de um conector de 14 pinos no padrão da Texas Instruments. Por isso, antes de conectar seu adaptador JTAG, tome cuidado com a pinagem do conector. Mais detalhes aqui sobre a pinagem do conector JTAG da Beagleboard-xM aqui.
ADAPTADOR JTAG
O adaptador JTAG é um conversor de sinais JTAG para uma interface de comunicação do PC (USB, RS232, porta paralela, etc), sendo que a interface USB é a mais comum.
Existem diversos adaptadores JTAG (tem uma lista bem completa aqui). Nos meus testes utilizarei o Flyswatter da Tin Can Tool.
Antes de comprar o adaptador JTAG, verifique se ele tem os drivers para o seu sistema operacional e se é suportado por sua ferramenta de debugging.
OPENOCD
O OpenOCD (Open On-Chip Debugger) é uma ferramenta que provê as funcionalidades de debugging e programação de memórias flash em dispositivos embarcados. Ela fala diretamente com adaptadores JTAG e suporta diversas arquiteturas como ARM7, ARM9, ARM Cortex-A8, ARM Cortex-A9, ARM Cortex-M, XScale, MIPS, etc. Uma lista completa dos adaptadores suportados encontra-se aqui.
O OpenOCD também é um middleware entre o debugger (GDB no nosso caso) e o adaptador JTAG (Flyswatter no nosso caso). Para isso, ele basicamente implementa o protocolo de comunicação remota do GDB para receber e converter os comandos do debugger e enviar estes comandos para o adaptador JTAG.
Uma documentação completa do OpenOCD pode ser acessada aqui.
GBD
O GDB (GNU Debugger) é o debugger padrão para sistemas Linux. Normalmente, o GDB é usado de forma isolada, mas ele também pode trabalhar numa arquitetura cliente/servidor. Neste caso, temos o cliente GDB rodando na máquina de desenvolvimento e o servidor do GDB rodando na máquina-alvo (hardware que se deseja depurar).
O servidor do GDB controla a execução da aplicação e o cliente do GDB provê a interface, se comunicando com o servidor via TCP/IP ou serial. Esta é a configuração que você usaria para depurar normalmente uma aplicação rodando em um dispositivo com Linux embarcado.
No caso de debugging com JTAG, a diferença é que o OpenOCD e o adaptador JTAG simulam o servidor do GDB (gdbserver).
Que tal agora um pouco de ação?
INSTALANDO AS FERRAMENTAS
Todos os testes foram realizados em uma máquina rodando o Ubuntu 12.04. Talvez você precise adaptar os procedimentos abaixo se estiver utilizando uma outra distribuição ou versão do Ubuntu.
Primeiro instale as dependências das ferramentas que iremos utilizar:
$ sudo apt-get install build-essential gcc gdb libusb-1.0.0 libconfuse0 libtool autoconf texinfo libusb-dev |
O nosso adaptador JTAG usa um chip da FTDI para fazer a conversão USB->JTAG, e para que o OpenOCD consiga conversar com ele, precisamos instalar a biblioteca libftdi. Para isso, execute os comandos abaixo:
wget http://www.intra2net.com/en/developer/libftdi/download/libftdi-0.20.tar.gz tar zxfv libftdi-0.20.tar.gz cd libftdi-0.20/ ./configure --libdir=/usr/local/lib64 make sudo make install |
Remova o parâmetro libdir no comando de configure se sua máquina for de 32 bits.
Agora vamos instalar o OpenOCD:
wget http://downloads.sourceforge.net/project/openocd/openocd/0.6.0/openocd-0.6.0.tar.bz2 tar jxfv openocd-0.6.0.tar.bz2 cd openocd-0.6.0/ ./configure --libdir=/usr/local/lib64 --enable-ft2232_libftdi make sudo make install mkdir ~/.openocd cp -av tcl/* ~/.openocd/ |
Remova também o parâmetro libdir no comando de configure se sua máquina for de 32 bits.
TESTANDO
Agora vamos fazer tudo isso funcionar. Você vai precisar da sua Beagleboard-xM com o bootloader e o kernel gravados no cartão SD. E vai precisar também de um toolchain e do kernel da Beagleboard-xM compilado em um diretório da sua máquina.
Conecte a Beagleboard-xM ao adaptador JTAG, à console serial e à alimentação. A minha ficou assim:
Primeiro alimente a Beagleboard-xM e interrompa o boot no U-Boot.
Agora abra um novo terminal e inicie o OpenOCD:
sudo openocd -f interface/flyswatter.cfg -f board/ti_beagleboard_xm.cfg |
Assim que você iniciar o OpenOCD, ele vai esperar por conexões de clientes, que podem ser realizadas de diversas formas, incluindo telnet e GDB.
Para testar a conexão com o OpenOCD, abra um novo terminal e se conecte por telnet:
$ telnet localhost 4444 |
Você deverá receber a interface de linha de comando do OpenOCD. Digite “help” para ver a quantidade de comandos disponíveis. Você pode por exemplo listar todos os registradores da CPU com o comando “reg“:
> reg ===== ARM registers (0) r0 (/32): 0x00000002 (dirty) (1) r1 (/32): 0x00000002 (2) r2 (/32): 0x48004D20 (3) r3 (/32): 0x00B71B00 (4) r4 (/32): 0x00000205 (5) r5 (/32): 0x4020162C (6) r6 (/32): 0x402015C8 (7) r7 (/32): 0x00000010 (8) r8 (/32): 0x00000003 (9) r9 (/32): 0x00000001 (10) r10 (/32): 0x402015B8 (11) r11 (/32): 0x00000000 (12) r12 (/32): 0x0001D622 (13) sp_usr (/32) (14) lr_usr (/32) (15) pc (/32): 0x402009B8 ... |
Mais exemplos de comandos do OpenOCD estão disponíveis aqui.
Encerre a seção de Telnet. Entre no diretório do kernel execute o gdb do toolchain de compilação cruzada:
$ arm-linux-gdb vmlinux |
E insira um breakpoint no ponto de entrada do kernel:
(gdb) hbreak *0x80008000 Hardware assisted breakpoint 1 at 0x80008000: file arch/arm/kernel/head.S, line 94. |
Se você tiver problemas com o comando acima, pode ser que sua imagem vmlinux não esteja com o endereço do ponto de entrada do kernel (load address) correto. Para corrigir isso, abra o linker script em “arch/arm/kernel/vmlinux.lds“, altere o load address para 0x80008000, e recompile o kernel. Você pode conferir se o load address esta correto com o comando abaixo:
$ readelf -S vmlinux | grep head.text [ 1] .head.text PROGBITS 80008000 008000 00019c 00 AX 0 0 4 |
Agora volte ao U-Boot e carregue o kernel:
OMAP3 beagleboard.org # boot |
Automaticamente, o gdb irá parar no breakpoint configurado, que é a primeira instrução executada pelo kernel:
Breakpoint 1, stext () at arch/arm/kernel/head.S:94 94 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode |
Agora é só depurar o kernel passo-a-passo! Divirta-se!
(gdb) step 96 mrc p15, 0, r9, c0, c0 @ get processor id (gdb) list 91 THUMB( .thumb ) @ switch to Thumb now. 92 THUMB(1: ) 93 94 setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode 95 @ and irqs disabled 96 mrc p15, 0, r9, c0, c0 @ get processor id 97 bl __lookup_processor_type @ r5=procinfo r9=cpuid 98 movs r10, r5 @ invalid processor (r5=0)? 99 THUMB( it eq ) @ force fixup-able long branch encoding 100 beq __error_p @ yes, error 'p' |
Happy hacking!
Sergio Prado