Depurando o Kernel do Linux com Kprobes

De Wiki do Veiga

Tabela de conteúdo

Intrudução

Kprobes permite registrar breakpoints capazes de parar dinamicamente em qualquer rotina do kernel e coletar informações de debug e desempenho.

Existem atualmente três tipos de probres:

  • kprobes: podem ser inseridos virtualmente em qualquer instrução do kernel.
  • jprobes: são inseridos no ponto de entrada de uma função do kernel e provê acesso aos seus argumentos.
  • kretprobes (return probes): são chamadas quando uma função do kernel especificada retorna.

Um descrição detalhada de como funciona cada tipo de probe pode ser obtida em Documentation/kprobes.txt.

Existem ferramentas de depuração superiores construídas em cima do Kprobes, como o SystemTap. No entanto, o SystemTap ainda não funciona redondo em sistemas cross-compilados e algumas arquiteturas. :-(

Este texto descreve como habilitar o Kprobes no kernel e como criar um probe simples.

Compilando o Kernel para Suportar Kprobes

Seleção da funcionalidade no make menuconfig:

General setup  --->
     [*] Kprobes

Resultado no .config:

CONFIG_KPROBES=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y

Interface via debugfs

Kprobes utiliza o debugfs file system como interface com o usuário.

Para montar o debugfs:

mkdir /debug
mount -t debugfs nodev /debug

O arquivo enabled permite habilitar ou desabilitar o Kprobes:

cat /debug/kprobes/enabled
1

Por default, todos os kprobes são habilitados.

# Habilitar os kprobes
echo 1 > /debug/kprobes/enabled
# Desabilitar os kprobes
echo 0 > /debug/kprobes/enabled

Uma lista de todos os kprobes habilitados pode ser visualizada em:

cat /debug/kprobes/list
c015d71a  k  vfs_read+0x0
c011a316  j  do_fork+0x0
c03dedc5  r  tcp_v4_rcv+0x0

Criação de kprobes

Os kprobes são inseridos no kernel tipicamente na forma de módulos. A seguir é descrito como criar um módulo do kernel que registra kprobes.

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>

static struct kprobe kp = {
        .symbol_name    = "do_fork",
};

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
        printk(KERN_INFO "pre_handler: p->addr = 0x%p\n", p->addr);
        return 0;
}

static void handler_post(struct kprobe *p, struct pt_regs *regs,
                                unsigned long flags)
{
        printk(KERN_INFO "post_handler: p->addr = 0x%p\n", p->addr);
}

static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
        printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn",
                p->addr, trapnr);
        return 0;
}

static int __init kprobe_init(void)
{
        int ret;
        kp.pre_handler = handler_pre;
        kp.post_handler = handler_post;
        kp.fault_handler = handler_fault;

        ret = register_kprobe(&kp);
        if (ret < 0) {
                printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
                return ret;
        }
        printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
        return 0;
}

static void __exit kprobe_exit(void)
{
        unregister_kprobe(&kp);
        printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
}

module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");

Criar Makefile para compilar o módulo:

cat > Makefile

Compilar e carregar o módulo:

make
ls kprobe_example.ko
insmode kprobe_example.ko
...

Referências


Marcelo Veiga Neves < marcelo.veiga at gmail.com >

Ferramentas pessoais