Depurando o kernel Linux com o KGDB na Beaglebone Black
- por Sergio Prado
Sim, da mesma forma que você depura uma aplicação, é possível também depurar o kernel Linux. E a resposta para esta funcionalidade é o KGDB.
O KGDB implementa o protocolo de comunicação remota do GDB, possibilitando a comunicação com um cliente GDB. Portanto, o processo de depuração com o KGDB acontece remotamente, com o host (máquina de desenvolvimento) conectado ao target (plataforma de hardware) por uma porta serial RS232 ou por uma interface de rede (via UDP/IP).
Atualmente, diversas arquiteturas suportam o KGDB, dentre elas x86, PPC, MIPS e ARM. Apenas o suporte à comunicação pela rede do KGDB ainda não foi integrado à versão oficial do kernel.
Vamos então colocar o KGDB para funcionar com a Beaglebone Black?
PREPARANDO O KERNEL
O primeiro passo é compilar o kernel com as opções abaixo habilitadas:
Kernel hacking ---> [*] KGDB: kernel debugger ---> <*> KGDB: use kgdb over the serial console [*] Compile the kernel with debug info |
Compile e atualize o kernel da Beaglebone Black.
PREPARANDO A COMUNICAÇÃO SERIAL
Como utilizaremos a porta serial tanto para acessar a console da Beaglebone Black quanto para depurar com o KGDB, é necessário uma aplicação de proxy para gerenciar o acesso à porta serial.
Para isso, já existe uma aplicação chamada agent-proxy, que pode ser compilada e executada conforme abaixo:
$ git clone git://git.kernel.org/pub/scm/utils/kernel/kgdb/agent-proxy.git/ $ cd agent-proxy/ $ make $ ./agent-proxy 5550^5551 0 /dev/ttyUSB0,115200 & |
Esta aplicação irá abrir duas portas TCP para conexão com a serial. No exemplo acima, a porta 5550 deve ser usada para a comunicação via console, e a porta 5551 para comunicação com o KGDB.
Use então o comando abaixo para iniciar a comunicação com a console da Beaglebone Black:
$ telnet localhost 5550 |
COLOCANDO O KERNEL NO MODO KGDB
Agora o próximo passo é colocar o kernel no modo KGDB.
Você pode fazer isso em tempo de boot, passando os parâmetros abaixo para o kernel:
kgdboc=ttyO0,115200n8 kgdbwait |
Ou então em tempo de execução, no terminal do target, executando os comandos abaixo:
# echo ttyO0,115200n8 > /sys/module/kgdboc/parameters/kgdboc # echo g > /proc/sysrq-trigger |
Em ambos os casos, o kernel deverá iniciar o KGDB e aguardar a conexão de um cliente GDB:
[ 0.944295] kgdb: Waiting for connection from remote gdb... |
USANDO O CLIENTE GDB
Agora é só iniciar o GDB do toolchain na máquina de desenvolvimento, passando a imagem vmlinux do kernel da Beaglebone Black.
$ arm-linux-gdb vmlinux |
E então se conectar com o KGDB pela porta 5551 do proxy:
(gdb) target remote localhost:5551 Remote debugging using localhost:5551 kgdb_breakpoint () at kernel/debug/debug_core.c:1012 1012 arch_kgdb_breakpoint(); |
O kernel deverá parar no breakpoint do KGDB. A partir daí é só usar o GDB normalmente.
Para continuar a execução do kernel, basta digitar “continue” ou apenas “c“:
(gdb) c |
Para parar a execução, pressione CTRL-C no terminal do GDB.
Você pode colocar um breakpoint em qualquer função:
(gdb) b omap_rtc_read_time (gdb) c |
O KGDB irá parar a execução quando o breakpoint for atingido:
Breakpoint 1, omap_rtc_read_time (dev=0xdf10ee10, tm=0xdf4c5ecc) at drivers/rtc/rtc-omap.c:220 220 { |
Você poderá executar o kernel passo-a-passo, inspecionar variáveis, imprimir um backtrace do stack, ou executar qualquer outra função disponível no GDB.
DEPURANDO COM O DDD
Se você se sentir mais confortável com uma ferramenta gráfica, pode usar também um frontend gráfico disponível para o GDB, como o Eclipse ou o DDD:
$ ddd --debugger gdb-multiarch vmlinux |
NEM TUDO SÃO FLORES
Durante os testes iniciais, não consegui fazer o GDB funcionar. Sempre que tentava me conectar com o KGDB, acontecia o erro abaixo:
Internal error: Oops - undefined instruction: 0 [#1] SMP THUMB |
Fiz algumas pesquisas e descobri que o GDB que eu estava usando utilizava algumas instruções que o kernel não suportava, e para resolver este problema precisei aplicar o patch abaixo no kernel:
diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 48066ce9ea34..4421df70141b 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -31,10 +31,17 @@ * Note to ARM HW designers: Add real trap support like SH && PPC to * make our lives much much simpler. :) */ +#ifdef CONFIG_THUMB2_KERNEL +#define BREAK_INSTR_SIZE 2 +#define KGDB_BREAKINST 0xdefe +#define KGDB_COMPILED_BREAK 0xdeff +#else #define BREAK_INSTR_SIZE 4 #define GDB_BREAKINST 0xef9f0001 #define KGDB_BREAKINST 0xe7ffdefe #define KGDB_COMPILED_BREAK 0xe7ffdeff +#endif + #define CACHE_FLUSH_IS_SAFE 1 #ifndef __ASSEMBLY__ diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index 778c2f7024ff..cc905fc52f08 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -157,14 +157,16 @@ static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned int instr) return 0; } +#define INSTR_MASK ((1ULL<<(BREAK_INSTR_SIZE * 8))-1) + static struct undef_hook kgdb_brkpt_hook = { - .instr_mask = 0xffffffff, + .instr_mask = INSTR_MASK, .instr_val = KGDB_BREAKINST, .fn = kgdb_brk_fn }; static struct undef_hook kgdb_compiled_brkpt_hook = { - .instr_mask = 0xffffffff, + .instr_mask = INSTR_MASK, .instr_val = KGDB_COMPILED_BREAK, .fn = kgdb_compiled_brk_fn }; |
Apesar dos testes terem sido realizados na Beaglebone Black, os procedimentos descritos neste artigo devem funcionar em qualquer outra plataforma de hardware que tenha uma porta serial e o kernel Linux com suporte ao KGDB.
Happy debugging!
Sergio Prado