Tema representativo de entrevista

Entrevista de código: ¿Cómo escribirías un trazador eBPF portable con CO-RE?

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Debes medir la latencia de syscalls en varias versiones del kernel de Linux. Explica cómo eBPF CO-RE proporciona portabilidad y diseña la compilación, la carga, la entrega de eventos y el plan de contingencia ante fallos.

Planteamiento y contexto

Un agente de observabilidad debe medir la latencia de syscalls en diversas distribuciones y versiones del kernel. Compilar por separado para cada kernel crea una matriz de versiones inmanejable, mientras que los offsets de estructuras hardcodeados se rompen tras las actualizaciones. Explica BTF, las reubicaciones de CO-RE y las responsabilidades de libbpf, y luego diseña una ruta verificable de compilación, carga, eventos y compatibilidad.

Qué está evaluando el entrevistador

  • Comprensión de la relación entre la información de tipos BTF, los registros de reubicación del compilador y el parcheo en tiempo de carga.
  • Distinción entre la portabilidad de layout y las restricciones del verificador, helpers, kfuncs y tipos de programas.
  • Diseño del ciclo de vida de los eventos, la entrega mediante ring-buffer o perf-buffer y el registro de pérdidas.
  • Manejo de ausencia de BTF, campos inexistentes, capacidades insuficientes y fallos de carga.

Preguntas para aclarar primero

  1. ¿El kernel de destino tiene BTF habilitado y expone /sys/kernel/btf/vmlinux?
  2. ¿Qué tipos de sondas están disponibles (tracepoints, kprobes, fentry o LSM) y cuáles son los límites de privilegios?
  3. ¿Cuáles son la tasa de eventos, el presupuesto de latencia, la pérdida tolerada y el modelo de consumo en user-space?
  4. ¿Qué arquitecturas, versiones de kernel y entornos de contenedores se deben soportar?
  5. Si un campo no está presente o el verificador rechaza el programa, ¿el agente debe deshabilitar la métrica o utilizar una sonda de respaldo?

Estructura de respuesta en 30 segundos

Compilaría un único objeto BPF con BTF y datos de reubicación CO-RE, y luego dejaría que libbpf parchee los offsets de los campos en tiempo de carga utilizando el BTF del kernel en ejecución. Un skeleton generado gestionaría los maps, los programas y el ring buffer mientras verifica capacidades, BTF y tipo de programa. Los campos ausentes o fallos de carga se registrarían y deshabilitarían la métrica o cambiarían a un tracepoint estable; el agente nunca emitiría valores inventados. Los benchmarks cubrirían múltiples kernels, eventos concurrentes, pérdidas y recuperación tras la descarga.

Respuesta detallada paso a paso

Paso 1: Separar las responsabilidades de compilación y de carga

El compilador escribe el programa BPF, la información de tipos y los registros de reubicación de CO-RE en el objeto. En tiempo de carga, libbpf utiliza el BTF del kernel de destino para resolver estructuras, campos y offsets, y actualiza los inmediatos o offsets de las instrucciones. CO-RE reduce las compilaciones por kernel, pero no hace que todos los helpers, kfuncs o tipos de programas estén disponibles.

Paso 2: Elegir un límite de sonda estable

Prioriza tracepoints estables o fentry y confirma los campos de eventos expuestos por el kernel de destino. Los kprobes cubren más rutas pero dependen más de los símbolos y del layout de parámetros. Expresa el acceso a campos con macros de CO-RE en lugar de offsets fijos. Lee solo los campos necesarios para reducir el trabajo del verificador y el tamaño del evento.

Paso 3: Diseñar la estructura del evento y el transporte

Registra el pid, la marca de tiempo, la identidad del syscall y los datos mínimos de latencia en BPF, y luego envíalos a través de un ring buffer o perf buffer. Utiliza un evento de tamaño fijo o un campo de versión explícito, y valida la longitud y la versión en user space. Rastrea la pérdida por CPU, proceso y cola; nunca conviertas silenciosamente el desbordamiento de búfer en latencia cero.

Paso 4: Integrar las restricciones del verificador en el diseño

Las comprobaciones de límites, los límites de bucles, los tipos de punteros y los valores de retorno de helpers deben satisfacer al verificador. Mueve el parseo complejo a user space y evita recorridos no demostrables en BPF. Asegúrate de que las definiciones de maps generadas por el compilador y el skeleton coincidan con los accesos del programa. Conserva resúmenes de fallos del verificador y versiones del kernel en los registros para diagnósticos.

Paso 5: Gestionar diferencias de BTF y campos

Verifica que el BTF de destino sea legible y utiliza campos opcionales de CO-RE o sondeos de características para detectar la presencia de campos. Si un cambio de layout hace fallar la reubicación, deshabilita la métrica o elige un tracepoint compatible; no llenes un campo ilegible con un valor por defecto para reportar una latencia precisa. Valida las diferencias de arquitectura en una matriz de CI multi-kernel.

Paso 6: Diseñar la ruta de compilación y lanzamiento

Fija las versiones de clang, libbpf y la generación de skeletons, produce un objeto que contenga BTF y registra su build ID. Distribuye el cargador en user-space, el objeto BPF y los requisitos mínimos de capacidades. Ejecuta autocomprobaciones antes de cargar. El despliegue en contenedores debe declarar los sistemas de archivos BPF, privilegios y parámetros del kernel requeridos, y proporcionar un motivo de fallo accionable.

Paso 7: Verificar rendimiento, corrección y rollback

Compara los valores de los campos y el recuento de eventos con una herramienta independiente a través de distribuciones, arquitecturas y kernels. Somete a prueba de estrés la CPU, la memoria, la pérdida en ring-buffer, el retraso de user-space y la latencia de cola con tasas altas de eventos. Conserva el objeto antiguo o una sonda estable como ruta de rollback y confirma que los enlaces, maps e hilos se liberen al descargar.

Respuesta de muestra de alta calidad

Compilaría un único objeto que contenga BTF y reubicaciones CO-RE y permitiría que libbpf parchee los offsets a partir del BTF del kernel de destino. Las sondas preferirían tracepoints estables o fentry; BPF emitiría un evento pequeño y versionado, y un skeleton en user-space lo decodificaría a través de un ring buffer mientras contabiliza las pérdidas. Las comprobaciones de inicio cubrirían BTF, tipo de programa, disponibilidad de helpers y presencia de campos. Un fallo de reubicación o del verificador deshabilitaría la métrica o cambiaría de sonda, nunca inventaría datos. El CI abarcaría arquitecturas, kernels, tasas altas de eventos y recuperación tras la descarga, reteniendo la sonda antigua para rollback.

Errores comunes

  • Tratar CO-RE como compatibilidad universal para cualquier helper, kfunc y restricción del verificador.
  • Hardcodear offsets de structs eludiendo BTF y las reubicaciones.
  • Omitir verificaciones de versión y longitud del evento, tratando datos truncados como válidos.
  • Ignorar la pérdida en el búfer y reportar un vacío de observación como latencia cero.
  • Rellenar campos faltantes con valores por defecto, ocultando un fallo de compatibilidad.

Preguntas de seguimiento y respuestas

Pregunta de seguimiento 1: ¿Cuándo ocurre la reubicación de CO-RE?

La compilación escribe los registros de reubicación en el objeto BPF. En tiempo de carga, libbpf los resuelve con el BTF del kernel de destino y parchea la información de los campos. Los offsets no se recalculan para cada evento en tiempo de ejecución.

Pregunta de seguimiento 2: ¿Puede ejecutarse sin vmlinux BTF?

Depende del BTF disponible, el tipo de sonda, la distribución y las capacidades de libbpf. Las comprobaciones de inicio deben determinar qué es seguro; si el campo no se puede resolver, se debe deshabilitar el programa o usar una sonda estable que no dependa de él.

Pregunta de seguimiento 3: ¿Por qué no usar kprobes en todas partes?

Los kprobes son flexibles pero dependen más de símbolos, parámetros e inlining, por lo que la semántica entre actualizaciones es menos estable. Los tracepoints o fentry pueden reducir el mantenimiento sin dejar de requerir comprobaciones de disponibilidad.

Pregunta de seguimiento 4: ¿Cómo demuestras que no faltan reportes?

Concilia el recuento de eventos BPF, las pérdidas en el ring-buffer, el consumo en user-space y un recuento independiente de syscalls por CPU y ventana de tiempo. Cualquier discrepancia debe manifestarse como un estado faltante o degradado, no convertirse silenciosamente en cero.

Pregunta de seguimiento 5: ¿Cómo depuras un rechazo del verificador?

Guarda los registros del cargador, la versión del kernel, la arquitectura de destino, el BTF ID y el resumen del verificador. Aísla el problema acotándolo a límites de punteros, límites de bucles, uso de helpers o definiciones de maps con un reproductor mínimo, y luego vuelve a ejecutar la matriz multi-kernel tras corregirlo.

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta