Depurando o kernel Linux com o KGDB na Beaglebone Black

- por Sergio Prado

Categorias: Beaglebone Black, Linux Tags: , ,

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

ddd-kgdb

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

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.