Depuração do kernel Linux O poder da depuração Depurar o kernel de um sistema operacional em funcionamento sempre foi uma coisa traiçoeira, mas com o emulador Qemu, isso pode não ser mais uma tarefa tão árdua. por Eva-Katharina Kunst e Jürgen Quade TUTORIAL Algumas das operações básicas que um depurador (ou debugger ) realiza em um sistema, incluem congelar sequências de código e subsequentemente analisar o conteúdo da memória RAM. Se a sequência de código pertencer a um aplicativo, a depuração, comparativamente, não envolve grandes problemas. Mas se você congelar o próprio kernel, não terá mais um ambiente de execução que aceite entrada do teclado, saída para um monitor, acesso ao conteúdo da memória ou que continue a executar o kernel depois. Podemos comparar a depuração do kernel com a tentativa de realizar uma cirurgia em si mesmo. De um ponto de vista técnico, esse problema é resolvido ao transferirmos funções complexas para um segundo sistema, que terá memória e gerenciamento de arquivos funcionando e ajudará você a pesquisar no código-fonte por variáveis, estruturas de dados, funções e linhas de código. Isso significa que é necessário apenas um servidor de depuração para o kernel a ser depurado. Este servidor pode executar comandos simples, como ler ou gravar células de memória ou definir pontos de parada de código ( breakpoints ) no sistema sob investigação. O emulador Qemu tem um servidor de depuração embutido ( quadro 1 ). Se você usar também o gerador de sistemas Buildroot [1], a depuração de kernel fica relativamente fácil de implementar. A condição para realizarmos isso é ter um kernel com símbolos de debug. Isso não é problema graças ao Buildroot: em pouco tempo, a ferramenta pode lhe dar um espaço do usuário bem definido e um kernel enxuto que pode ser rapidamente reconfigurado e modificado. Todas as etapas para essa abordagem são mostradas no quadro 2. Você pode começar baixando o Buildroot e descompactando o pacote. Depois, crie a configuração padrão preferivelmente para um sistema x86 digitando qemu_x86_defconfig. É preciso modificar quatro opções após digitar menuconfig : Em Toolchain, habilite a opção Build gdb para Host ; em Kernel/ Kernel version, digite 3.2 ; em System Configuration/Port to run getty (login prompt) on, digite tty1 ; e finalmente para Build Options/Number of jobs to run simultaneously, digite o número de núcleos da máquina geradora. Figura 1 No Linux há diversas opções para depuração de código do kernel e dos módulos: o emulador Qemu, o kgdb e o kdb 69
TUTORIAL Depuração do kernel Linux Figura 2 As opções do kernel necessárias para depuração estão localizadas no menu Kernel hacking. Figura 3 O GDB é iniciado no diretório do código-fonte Linux para ter acesso aos arquivos C. Digitar um outro dispara a execução do primeiro gerador. Isso criará o código-fonte do kernel, entre outras coisas, mas será necessário modificar a configuração novamente para depurar o kernel em algum momento no futuro. Para fazê-lo, execute linux-menuconfig no diretório raiz do Buildroot. As opções relevantes no menu subsequente estão localizadas no item Kernel Hacking (figura 2 ). As opções Kernel debugging e Compile the kernel with debug info são necessárias. Digitar mais um gera um kernel com a configuração modificada. O resultado disso basicamente se resume a dois arquivos: você terá um arquivo vmlinux que contém tanto o código quanto as informações de depuração correspondentes no diretório do código-fonte do kernel. O subdiretório de arquitetura arch/x86/boot/ para a plataforma x86 contém o kernel compactado no arquivo bzimage. Outras plataformas podem chamar o kernel de zimage. Carregadores de boot, como o GRUB, precisam do kernel compactado ( bzimage ). O depurador em si também precisa da imagem do kernel, mas usará a versão não compactada vmlinux, que Listagem 1: Makefi le 01 ifneq ($(KERNELRELEASE),) 02 obj-m := hello.o 03 04 else 05 PWD:= $(shell pwd) 06 KDIR:= ~/buildroot-2011.08/output/build/linux-3.1/ 07 08 default: 09 $(MAKE) -C $(KDIR) M=$(PWD) modules 10 endif 11 12 clean: 13 rm -rf *.ko *.o *.mod.c *.mod.o modules.order 14 rm -rf Module.symvers.*.cmd.tmp_versions contém as informações de depuração. Obviamente, o depurador também necessita de acesso ao código-fonte. Assim que gerar o kernel e o sistema de arquivos principal com o Buildroot, é necessário em primeiro lugar testá-los sem depuração, o que pode ser feito com o comando: qemu -kernel output/images/bzimage -hda output/images/rootfs.ext2 -append "root=/dev/sda rw" Se tudo funcionar, você pode iniciar a depuração acrescentando os parâmetros -s e -S : qemu -kernel output/images/bzimage -hda output/images/rootfs.ext2 -append "root=/dev/sda rw" -s -S A opção -s inicia o servidor de depuração ( gdbserver ), e -S para o kernel no início. Mudança de página Para o GNU debugger (GDB) encontrar o código C do kernel e dos arquivos de cabeçalho, inicie a ferramenta no diretório do código-fonte do Linux ( figura 3 ). Se você compilou um sistema x86, pode usar o GNU 70 www.linuxmagazine.com.br
debugger pré-instalado no sistema de desenvolvimento; senão, use o GDB compilado para o sistema host; que reside no diretório Buildroot output/ host/usr/bin : cd output/build/linux-3.2/gdb O comando gdb inicia a sessão de depuração. Para começar, carregue o código do kernel e os símbolos com o comando file vmlinux. Se surgir a mensagem no debug-symbols found, será necessário verificar as opções de depuração em sua configuração do kernel e, possivelmente, recompilá- -lo. Com os símbolos, o vmlinux não é maior que 40 MB. Em seguida, abra uma conexão com o servidor de depuração digitando target remote :1234 (figura 3 ). O comando gdb, então, prossegue com o curso da execução ( tabela 1 ). O comando continue inicia o sistema Linux em modo convidado e pressionar as teclas [Ctrl] + [C] interrompe a execução. A figura 3 mostra o comando GDB break vfs_mknod definindo um breakpoint para a função vfs_mknod. Para o kernel 3.2, use sys_mknod em vez disso, devido a mudanças no kernel Linux. Quando um usuário no Linux executa o comando mknod /dev/hello c 254 0, a execução é paralisada e você pode inspecionar as variáveis de ambiente. Para continuar a execução do programa, use o comando continue. Para isolar o depurador do sistema Linux, primeiro pressione as teclas [Ctrl] + [C] e depois use o comando GDB detach para interromper a conexão com o servidor. Digitar quit finaliza o depurador. Módulos Os módulos do kernel também podem ser depurados no nível da linguagem de programação com o Qemu. Para fazê-lo, é preciso ativar a opção Enable loadable module support no submenu Module unloading. Em outras palavras, esse processo envolve reconfigurar e gerar novamente o ker- Listagem 2: Módulo hello.c 01 #include <linux/fs.h> 02 #include <linux/cdev.h> 03 #include <linux/device.h> 04 #include <linux/module.h> 05 #include <asm/uaccess.h> 06 07 static char hello_world[]="hello World\n"; 08 static dev_t hello_dev_number; 09 static struct cdev *driver_object; 10 static struct class *hello_class; 11 static struct device *hello_dev; 12 13 static ssize_t driver_read(struct file *instanz,char user *user, size_t count, loff_t *offset) 14 { 15 unsigned long not_copied, to_copy; 16 17 to_copy = min(count, strlen(hello_world)+1); 18 no_copied=copy_to_user(user,hello_world,to_copy); 19 20 } 21 22 static struct file_operations fops = { 23.owner= THIS_MODULE, 24.read= driver_read, 25 }; 26 27 static int init mod_init( void ) 28 { 29 if (alloc_chrdev_region(&hello_dev_number,0,1,"hello")<0) 30 return -EIO; 31 driver_object = cdev_alloc(); 32 if (driver_object==null) 33 goto free_device_number; 34 driver_object->owner = THIS_MODULE; 35 driver_object >ops = &fops; 36 if (cdev_add(driver_object, hello_dev_number,1)) 37 goto free_cdev; 38 hello_class = class_create( THIS_MODULE, "Hello" ); 39 if (IS_ERR( hello_class )) { 40 pr_err( "hello: no udev support\n"); 41 goto free_cdev; 42 } 43 hello_dev = device_create( hello_class, NULL, hello_dev_number, NULL, "%s", "hello" ); 44 return 0; 45 free_cdev: 46 kobject_put( &driver_object >kobj ); 47 free_device_number: 48 unregister_chrdev_region( hello_dev_number, 1 ); 49 return -EIO; 50 } 51 52 static void exit mod_exit( void ) 53 { 54 device_destroy( hello_class, hello_dev_number ); 55 class_destroy( hello_class ); 56 cdev_del( driver_object ); 57 unregister_chrdev_region( hello_dev_number, 1 ); 58 return; 59 } 60 61 module_init( mod_init ); 62 module_exit( mod_exit ); 63 MODULE_LICENSE("GPL"); 71
TUTORIAL Depuração do kernel Linux nel que você criou com o Buildroot. Como também é impossível prever o endereço do código do módulo na memória principal, você precisa carregar o módulo, encontrar o endereço e informar ao depurador. Será preciso verificar as entradas no sistema de arquivos /sys para fazê-lo, mas trataremos disso posteriormente. Antes da depuração, primeiro gere o módulo para o kernel criado com o Buildroot. A maneira mais fácil de fazer isso é usar um arquivo Makefile modificado, apontando a variável KDIR para o caminho com o código-fonte Linux que você está usando, que estará abaixo do diretório raiz, neste caso. Se o sistema Linux no Qemu que você está depurando não for projetado para uma arquitetura x86, será necessário definir as variáveis de ambiente CROSS_COMPILE e ARCH. Para depurar o módulo, é preciso usar a listagem 1 como Makefile e a listagem 2 como arquivo hello.c em uma pasta abaixo do diretório raiz do Buildroot. Você também pode precisar modificar o caminho para o código-fonte Linux na variável KDIR. O Makefile modificado gera o módulo hello.ko, que você pode então copiar para o sistema de arquivos raiz: cp hello.ko../output/target/root/ Um no diretório Buildroot gera novamente o sistema de arquivos raiz. Isso coloca o módulo no diretório home do usuário root após a inicialização. A abordagem mais fácil é omitir a opção -S e usar -s quando o Qemu for inicializado. Isso habilita Figura 4 Após entrar como usuário root, carregue o módulo do kernel no Qemu e determine os endereços do código e dos segmentos de dados. Comando Função Add-symbol-file Carrega arquivo de símbolos adicional break Defi ne um breakpoint bt Retorna a sequência de chamadas para funções continue Continua o programa del Apaga um breakpoint criado detach Faz logout do depurador no servidor de depuração file Carrega código (programa) help Retorna dados sobre as funções implementadas info Exibe diversas informações next Processa linha de código print Exibe a saída de variáveis, estruturas de dados e células de memória quit Termina o GDB Tabela 1 Comandos GDB importantes. o servidor de depuração, mas o Linux ainda inicializará diretamente. Após logar-se como root, carregue o módulo com o comando insmod hello.ko (figura 4 ). Os comando seguintes determinam os endereços dos segmentos de código e os dois segmentos de dados: # cat /sys/module/hello/sections/.text # cat /sys/module/hello/sections/.data # cat /sys/module/hello/sections/.bss Como o sistema Linux gerado pelo Buildroot não inclui compatibilidade udev, você também precisa usar Quadro 1: Opções de depuração do kernel No Linux, há três abordagens para depuração de código do kernel e seus módulos: o Qemu, o kgdb e o kdb ( figura 1 ). Embora o Qemu não precise de nenhum suporte especial no kernel, Linus Torvalds relutantemente aceitou o servidor de depuração embutido no kernel, kgdb, há três anos. Assim como no Qemu, o depurador em si é executado em um segundo computador, o host de depuração. A comunicação entre o servidor e o depurador utiliza uma conexão serial. Se você não tem nem uma segunda máquina nem uma conexão serial, pode usar uma solução de virtualização como o VirtualBox para emular a interface requerida. Além disso, Torvalds integrou ao kdb, um frontend do kgdb, na versão 2.6.35 do kernel para haver compatibilidade com operações simples, como ler e defi nir endereços de memória, ler mensagens do kernel ou mostrar os processos de computação instanciados todos acontecendo no mesmo sistema. Caso seja confi gurada para isso, a ferramenta kdb é habilitada automaticamente quando o kernel sofre alguma pane ( kernel crash ). De outro modo, você pode pressionar o atalho de teclado [Alt] + [PrtScr] + [g]. Contudo, isso ainda não permite que os desenvolvedores depurem o kernel no nível da linguagem de programação. 72 www.linuxmagazine.com.br
o comando mknod /dev/hello c 254 0 para criar o arquivo de dispositivos externos, que um aplicativo usaria para acessar um driver neste exemplo. Você pode descobrir se o sistema usa um número maior do que 254 digitando o comando cat /proc/devices grep Hello. Depois, inicie o GDB no sistema host, normalmente a partir do diretório fonte do kernel. Após digitar os comandos file vmlinux e Quadro 2: Breve guia do Buildroot target remote :1234, o Qemu paralisa o sistema Linux. O seguinte comando informa ao sistema o endereço hexadecimal do segmento de código e dos dois segmentos de dados: add-symbol-file path/hello.ko 0xd8817000 -s.data 0xd88170e0 -s.bss 0xd8817294 Agora, você pode definir um breakpoint, por exemplo, para a Várias etapas são necessárias para tornar um ambiente apto para depurar o kernel Linux e seus módulos. 1. Baixe e descompacte o Buildroot: wget http://buildroot.uclibc.org/downloads/buildroot-2011.11.tar.bz2 tar xvfj buildroot-2011.11.tar.bz2 2. Confi gure o Buildroot e gere o sistema: cd buildroot-2011.11 qemu_x86_defconfig menuconfig (build-option, Kernel-Version 3.2, gdb, tty1) linux-menuconfig (debug info) 3. Compile o módulo e copie-o no diretório raiz: cd driver export CROSS_COMPILE=... export ARCH=... cp module.ko.../output/target/root/ 4. Gere novamente o sistema de arquivos principal: cd buildroot-2011.11 5. Inicie o sistema com o Qemu e o servidor de depuração: qemu -kernel output/images/bzimage -hda output/images/rootfs.ext2 -append "root=/dev/sda rw" -s -S & 6. Inicie o depurador: gdb Sessão do depurador: file vmlinux target remote :1234 continue 7. Faça login, carregue o driver e identifi que os endereços de memória (por exemplo, para hello.ko ): insmod hello.ko cat /sys/module/hello/sections/.text cat /sys/module/hello/sections/.data cat /sys/module/hello/sections/.bss 8. Carregue os dados de símbolos do módulo: add-symbol-file module.ko text_address -s.data data_address -s.bss bss_address 9. Depuração: defi na os pontos de parada de código ( breakpoints ) ou realize uma depuração personalizada. função driver_read. O comando continue pede para o sistema Linux voltar ao trabalho. Se você digitar o comando cat /dev/hello no terminal do sistema que está depurando, verá que agora o driver_read está habilitado e o GDB interrompe o kernel no ponto definido antes. Você pode, então, investigar as células de memória do módulo e acompanhar as etapas do processo. Conclusão Pode ser confuso ver o depurador saltar entre as linhas de código, aparentemente sem motivo. O acesso às variáveis locais explica a razão disso: value optimized out o compilador otimizou o código do kernel. Isso significa que algumas das variáveis definidas estão invisíveis para o depurador; fragmentos de código foram remodelados. Como algumas macros estão sendo usadas no kernel, a resolução de problemas também não é nem um pouco facilitada. Em muitos casos, descobrimos que uma variável na verdade é uma macro inteligente. Assim, a depuração do kernel continua a ser um desafio que exige muita paciência e prática. Mais informações [1] Download do Buildroot: http://buildroot.uclibc. org/download.html Os autores Eva-Katharina Kunst, jornalista, e Jürgen Quade, professor da Faculdade do Baixo Reno (Alemanha), são fãs do Código Aberto desde os primórdios do Linux. Juntos, publicaram o livro Desenvolvimento de drivers no Linux, sobre o kernel 2.6. Gostou do artigo? Queremos ouvir sua opinião. Fale conosco em cartas@linuxmagazine.com.br Este artigo no nosso site: http://lnm.com.br/article/6939 73