bpftrace para Trazado de Sistema y Depuración en Linux

bpftrace es un lenguaje de trazado de alto nivel para Linux basado en eBPF (extended Berkeley Packet Filter), que permite trazar dinámicamente el kernel, las llamadas al sistema y las aplicaciones en espacio de usuario sin necesidad de reiniciar el sistema ni modificar código. Con una sintaxis inspirada en awk y DTrace, bpftrace permite responder preguntas difíciles de rendimiento y depuración que herramientas tradicionales como strace o perf no pueden responder eficientemente. Esta guía cubre los tipos de sondas, one-liners esenciales y la escritura de scripts de trazado.

Requisitos Previos

  • Kernel Linux 4.9+ (recomendado 5.4+ para todas las funcionalidades)
  • Cabeceras del kernel instaladas
  • Acceso root (eBPF requiere privilegios CAP_BPF o root)
  • Para aplicaciones de espacio de usuario: símbolos de depuración o binarios sin strip
# Verificar versión del kernel
uname -r

# Verificar soporte eBPF
ls /sys/kernel/debug/tracing/events/ | head -10

Instalación de bpftrace

Ubuntu/Debian

# Instalar cabeceras del kernel
apt install linux-headers-$(uname -r)

# Ubuntu 20.04+
apt install bpftrace

# Para la versión más reciente (PPA)
apt-add-repository ppa:hadret/bpftrace
apt update && apt install bpftrace

# Verificar instalación
bpftrace --version

CentOS/Rocky Linux

# Instalar dependencias
dnf install bpftrace bpftool kernel-devel-$(uname -r)

# Habilitar kernel debug filesystem
mount -t debugfs debugfs /sys/kernel/debug

# Añadir al arranque (para que persista)
echo "debugfs /sys/kernel/debug debugfs defaults 0 0" >> /etc/fstab

Verificar Herramientas Incluidas

# bpftrace incluye scripts de ejemplo útiles
ls /usr/share/bpftrace/tools/
# biolatency.bt  execsnoop.bt  opensnoop.bt  tcpconnect.bt ...

# Ejecutar los scripts de ejemplo directamente
bpftrace /usr/share/bpftrace/tools/opensnoop.bt

Tipos de Sondas (Probes)

bpftrace soporta múltiples tipos de sondas para diferentes puntos de instrumentación:

TipoFormatoDescripción
kprobekprobe:función_kernelEntrada de función del kernel
kretprobekretprobe:función_kernelRetorno de función del kernel
tracepointtracepoint:subsistema:eventoPuntos de traza estáticos del kernel
uprobeuprobe:/ruta/binario:funciónEntrada de función en espacio de usuario
uretprobeuretprobe:/ruta/binario:funciónRetorno de función en espacio de usuario
usdtusdt:/ruta:proveedor:sondaSondas estáticas definidas por usuario
softwaresoftware:evento:frecuenciaEventos de software del kernel
hardwarehardware:evento:frecuenciaContadores de hardware del CPU
profileprofile:hz:NMuestreo a N Hz
intervalinterval:s:NIntervalo periódico
# Listar todos los kprobes disponibles
bpftrace -l 'kprobe:*tcp*'

# Listar tracepoints disponibles
bpftrace -l 'tracepoint:syscalls:*'
bpftrace -l 'tracepoint:net:*'

# Listar funciones de un binario específico
bpftrace -l 'uprobe:/usr/bin/python3:*' | head -20

One-liners Esenciales

# Trazar todas las llamadas open() con archivo y proceso
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'

# Mostrar procesos que ejecutan nuevos comandos (como execsnoop)
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%-10s %-6d %s\n", comm, pid, str(args->filename)); }'

# Contar llamadas al sistema por proceso
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

# Medir latencia de lecturas de disco
bpftrace -e 'kprobe:blk_account_io_start { @start[arg0] = nsecs; }
kprobe:blk_account_io_done /@start[arg0]/ { @latencia_us = hist((nsecs - @start[arg0]) / 1000); delete(@start[arg0]); }'

# Trazar conexiones TCP nuevas
bpftrace -e 'kprobe:tcp_connect { printf("Conexión TCP: PID=%d proceso=%s\n", pid, comm); }'

# Monitorear uso de memoria en tiempo real
bpftrace -e 'kprobe:mm_page_alloc { @allocs[comm] = sum(1); } interval:s:5 { print(@allocs); clear(@allocs); }'

# Detectar procesos que leen /etc/passwd (posible reconocimiento)
bpftrace -e 'tracepoint:syscalls:sys_enter_openat /str(args->filename) == "/etc/passwd"/ { printf("ALERTA: %s (PID:%d) leyó /etc/passwd\n", comm, pid); }'

Trazado del Kernel

Tracepoints Estáticos (más estables)

# Trazar eventos de E/S de bloque
bpftrace -e '
tracepoint:block:block_rq_issue {
    printf("%-10s %-6d %-6s %d bytes\n", comm, pid, args->rwbs, args->bytes);
}'

# Monitorear asignaciones y liberaciones de memoria del kernel
bpftrace -e '
tracepoint:kmem:kmalloc {
    @kmalloc_bytes[comm] = sum(args->bytes_alloc);
}
interval:s:10 {
    printf("=== Top asignaciones kmalloc (10s) ===\n");
    print(@kmalloc_bytes, 10);
    clear(@kmalloc_bytes);
}'

# Trazar eventos de planificación del CPU
bpftrace -e '
tracepoint:sched:sched_switch {
    @context_switches[args->prev_comm] = count();
}
interval:s:5 {
    printf("Context switches por proceso (5s):\n");
    print(@context_switches, 10);
    clear(@context_switches);
}'

kprobes Dinámicos

# Trazar llamadas do_sys_open con latencia
bpftrace -e '
kprobe:do_sys_open {
    @ts[tid] = nsecs;
    @files[tid] = arg1;  /* nombre del archivo */
}
kretprobe:do_sys_open /@ts[tid]/ {
    $latencia = (nsecs - @ts[tid]) / 1000;
    if ($latencia > 100) {  /* solo aberturas lentas >100µs */
        printf("%-16s latencia:%dµs retval:%d\n", comm, $latencia, retval);
    }
    delete(@ts[tid]);
    delete(@files[tid]);
}'

Trazado de Espacio de Usuario

# Trazar funciones de Python (requiere binario con símbolos)
bpftrace -e 'uprobe:/usr/bin/python3:PyEval_EvalFrameEx { printf("Python frame en PID %d\n", pid); }'

# Trazar llamadas malloc y free en un proceso (detectar fugas de memoria)
PID_OBJETIVO=1234
bpftrace -e "
uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /pid == $PID_OBJETIVO/ {
    @allocs[ustack] = sum(arg0);
}
uprobe:/lib/x86_64-linux-gnu/libc.so.6:free /pid == $PID_OBJETIVO/ {
    @frees = count();
}
interval:s:10 {
    printf(\"Asignaciones activas:\\n\");
    print(@allocs, 5);
}"

# Trazar funciones de Nginx
bpftrace -e 'uprobe:/usr/sbin/nginx:ngx_http_process_request { printf("Solicitud HTTP en PID:%d\n", pid); }'

Scripts de bpftrace

Script de Latencia de Syscalls

cat > /usr/local/bin/syscall-latency.bt << 'EOF'
#!/usr/bin/bpftrace
/*
 * Medir latencia de syscalls por proceso
 * Uso: bpftrace syscall-latency.bt [nombre-proceso]
 */

tracepoint:raw_syscalls:sys_enter {
    @entrada[tid] = nsecs;
}

tracepoint:raw_syscalls:sys_exit /@entrada[tid] != 0/ {
    $latencia = nsecs - @entrada[tid];
    @latencias[comm] = hist($latencia);
    delete(@entrada[tid]);
}

interval:s:10 {
    printf("=== Distribución de latencia de syscalls (10s) ===\n");
    print(@latencias);
    clear(@latencias);
}
EOF
chmod +x /usr/local/bin/syscall-latency.bt

# Ejecutar
bpftrace /usr/local/bin/syscall-latency.bt

Script de Top de I/O por Proceso

cat > /usr/local/bin/io-top.bt << 'EOF'
#!/usr/bin/bpftrace
/*
 * Top de I/O de disco por proceso
 * Uso: bpftrace io-top.bt
 */

kprobe:vfs_read {
    @lecturas_bytes[comm, pid] = sum(arg2);
}

kprobe:vfs_write {
    @escrituras_bytes[comm, pid] = sum(arg2);
}

interval:s:5 {
    printf("\n=== Top 10 procesos por I/O (5s) ===\n");
    printf("--- Lecturas ---\n");
    print(@lecturas_bytes, 10);
    printf("--- Escrituras ---\n");
    print(@escrituras_bytes, 10);
    clear(@lecturas_bytes);
    clear(@escrituras_bytes);
}
EOF
chmod +x /usr/local/bin/io-top.bt
bpftrace /usr/local/bin/io-top.bt

Script de Detección de Actividad Sospechosa

cat > /usr/local/bin/security-watch.bt << 'EOF'
#!/usr/bin/bpftrace
/*
 * Monitoreo de seguridad con bpftrace
 * Detecta actividad sospechosa: escalada de privilegios, acceso a archivos sensibles
 */

/* Detectar setuid() - posible escalada de privilegios */
tracepoint:syscalls:sys_enter_setuid {
    printf("[SEGURIDAD] setuid(%d) llamado por %s (PID:%d UID:%d)\n",
        args->uid, comm, pid, uid);
}

/* Detectar acceso a archivos de contraseñas */
tracepoint:syscalls:sys_enter_openat {
    $archivo = str(args->filename);
    if ($archivo == "/etc/shadow" || $archivo == "/etc/passwd") {
        printf("[SEGURIDAD] %s abrió %s (PID:%d UID:%d)\n",
            comm, $archivo, pid, uid);
    }
}

/* Detectar ejecución de shells */
tracepoint:syscalls:sys_enter_execve {
    $cmd = str(args->filename);
    if ($cmd == "/bin/sh" || $cmd == "/bin/bash" || $cmd == "/bin/zsh") {
        printf("[SEGURIDAD] Shell iniciado: %s por %s (PID:%d PPID:%d)\n",
            $cmd, comm, pid, curtask->real_parent->tgid);
    }
}

/* Contador de alertas por intervalo */
interval:s:60 {
    printf("[RESUMEN] Intervalo de monitoreo completado: %s\n", strftime("%H:%M:%S", nsecs));
}
EOF
chmod +x /usr/local/bin/security-watch.bt

Análisis de Rendimiento

Flamegraph de CPU con bpftrace

# Generar datos de flamegraph con bpftrace (muestra pilas de CPU)
bpftrace -e '
profile:hz:99 {
    @[kstack, ustack, comm] = count();
}
interval:s:30 {
    exit();
}' > /tmp/bpftrace-cpu.txt

# Si tienes FlameGraph instalado
# git clone https://github.com/brendangregg/FlameGraph /opt/flamegraph
# bpftrace -e 'profile:hz:99 { @[kstack] = count(); } interval:s:30 { exit(); }' | \
#     /opt/flamegraph/stackcollapse-bpftrace.pl | \
#     /opt/flamegraph/flamegraph.pl > /tmp/cpu-flamegraph.svg

# Profiling de un proceso específico
bpftrace -e "
profile:hz:99 /pid == $PID_OBJETIVO/ {
    @[ustack, comm] = count();
}
interval:s:30 { exit(); }"

Análisis de Latencia de Red

# Medir latencia de conexiones TCP
bpftrace -e '
kprobe:tcp_connect {
    @inicio[tid] = nsecs;
}
kretprobe:tcp_connect {
    $latencia = nsecs - @inicio[tid];
    @latencias_tcp_ms = hist($latencia / 1000000);
    delete(@inicio[tid]);
}
interval:s:30 {
    printf("Histograma de latencia de conexiones TCP (ms):\n");
    print(@latencias_tcp_ms);
    exit();
}'

Solución de Problemas

Error "Could not open debugfs":

# Montar debugfs
mount -t debugfs debugfs /sys/kernel/debug
# Verificar
ls /sys/kernel/debug/tracing/

Error "Unknown probe type" o sonda no encontrada:

# Verificar que la función existe en el kernel
bpftrace -l 'kprobe:tcp_*' | grep -i connect
# Las funciones del kernel pueden variar entre versiones
# Usar tracepoints en lugar de kprobes cuando sea posible (más estables)
bpftrace -l 'tracepoint:net:*'

bpftrace no muestra resultados de uprobes:

# Verificar que el binario tiene símbolos
nm /usr/bin/python3 | head -10
# Si está stripped, instalar paquete de debug
apt install python3-dbg  # Ubuntu
# Verificar la ruta correcta del binario
which python3

Script produce demasiada salida y satura el buffer:

# Aumentar el tamaño del buffer del mapa
bpftrace --unsafe -e 'BEGIN { @mapa[1] = count(); } ...'
# O usar filtros más estrictos para reducir eventos
bpftrace -e 'tracepoint:syscalls:sys_enter_openat /pid > 1000/ { ... }'

Conclusión

bpftrace es una herramienta de observabilidad extraordinariamente poderosa que permite responder preguntas complejas sobre el comportamiento del sistema en producción sin impacto significativo en el rendimiento ni necesidad de recompilar código. Desde detectar fugas de memoria y cuellos de botella de I/O hasta monitorear actividad de seguridad en tiempo real, bpftrace se ha convertido en una herramienta indispensable para administradores de sistemas y desarrolladores que operan infraestructura Linux crítica.