# BPF CO-RE - Compile Once Run Everywhere _Процессы и ресурсы · LinuxLab Knowledge Base_ **TL;DR:** CO-RE - один скомпилированный eBPF-объект работает на разных kernel благодаря BTF (BPF Type Format). vmlinux.h - dump структур ядра. libbpf на runtime перезаписывает offsets. Заменяет BCC, не нужен LLVM в проде. ## Зачем CO-RE Раньше [[ebpf-basics|eBPF]] программы привязывались к конкретному kernel - structure layout менялся между версиями. Если writeable (`task_struct->pid`) перенесли на другой offset - программа сломалась. Решений было два: 1. **kernel-headers attached** - программа собирается под целевой kernel (BCC: компиляция в runtime через LLVM в каждом deploy) 2. **CO-RE (Compile Once - Run Everywhere)** - программа собирается один раз, на runtime libbpf делает relocations через BTF Сегодня CO-RE - стандарт. BCC объявлен legacy, новые tracing-инструменты пишут на libbpf+CO-RE. ## BTF - BPF Type Format **BTF** - компактный формат метаданных о типах (как DWARF, но специально для BPF). Описывает structs, unions, enums, function signatures. Kernel экспортирует свой BTF в `/sys/kernel/btf/vmlinux` (с `CONFIG_DEBUG_INFO_BTF=y`, в большинстве дистрибутивов с 5.4+). Размер - около 5-7 МБ против 200+ МБ для DWARF debug-info. Это позволяет держать его постоянно в kernel. Каждый загруженный module тоже имеет свой BTF в `/sys/kernel/btf/`. Userspace проверка: ``` ls /sys/kernel/btf/ bpftool btf dump file /sys/kernel/btf/vmlinux | head -20 ``` ## vmlinux.h - заголовок всего kernel Из BTF собирается **vmlinux.h** - один большой header со всеми структурами kernel: ``` bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h ``` Эта строка - вся включенная: вместо `#include ` ты пишешь `#include "vmlinux.h"` и получаешь все типы. Не надо ставить kernel-headers, не надо думать про CONFIG_*. Минусы: 50K+ строк, дольше компиляция. Зато не нужен kernel-source. ## CO-RE relocations Идея: clang при компиляции eBPF не "запекает" offset поля в bytecode, а оставляет специальный **CO-RE relocation record**. На runtime libbpf читает target-kernel BTF, находит соответствующее поле, переписывает offset в загруженной программе. Помечаешь access через `BPF_CORE_READ` или `__builtin_preserve_access_index`: ```c #include struct task_struct *task = (struct task_struct *)bpf_get_current_task(); pid_t pid = BPF_CORE_READ(task, pid); pid_t parent_pid = BPF_CORE_READ(task, parent, pid); ``` В bytecode: вместо "load байты по offset 0x4F8" - "load по offset(struct task_struct, pid)" с relocation record. На runtime libbpf проверяет в target BTF где `pid` лежит в `task_struct` и подставляет правильный offset. Если поле переименовано - можно использовать `BPF_CORE_READ_INTO` с fallback'ами, или `__attribute__((preserve_access_index))` структуры. ## Field-existence checks CO-RE умеет работать с **отсутствующими** в target-kernel полями: ```c if (bpf_core_field_exists(task->some_new_field)) { val = BPF_CORE_READ(task, some_new_field); } else { val = -1; // graceful fallback } ``` То же для **enum значений**: ```c if (bpf_core_enum_value_exists(enum cpu_state, CPU_STATE_NEW)) { ... } ``` Это даёт настоящую portability - один бинарник работает от kernel 5.4 до 6.10, gracefully degraded'ит features. ## libbpf-bootstrap - стартовый шаблон github.com/libbpf/libbpf-bootstrap - канонический шаблон проекта на libbpf+CO-RE. Структура: ``` myproject/ src/ myprog.bpf.c # eBPF-программа на C myprog.c # userspace-loader Makefile # генерирует skeleton, собирает оба libbpf/ # submodule с библиотекой bpftool/ # submodule ``` Workflow: 1. `clang -O2 -target bpf -c myprog.bpf.c -o myprog.bpf.o` 2. `bpftool gen skeleton myprog.bpf.o > myprog.skel.h` 3. Userspace `myprog.c` includ'ит skeleton, вызывает `myprog__open()`, `myprog__load()`, `myprog__attach()` 4. Финальный binary - один статически слинкованный исполняемый файл с embedded BPF-bytecode Деплой: один файл, нет dependency на kernel-headers, BCC, LLVM. ## CO-RE vs BCC - сравнение | Свойство | BCC | libbpf+CO-RE | |----------|-----|--------------| | Что нужно на target | LLVM (~600 МБ), kernel-headers | ничего (только kernel с BTF) | | Размер артефакта | Python + C source | один statically-linked binary | | Время старта | секунды (LLVM compile) | миллисекунды | | Portability | привязан к target kernel | один бинарник на много kernel | | Сложность написания | проще (high-level Python) | сложнее (C, skeleton) | | Языки userspace | Python | C, Go (libbpfgo), Rust (libbpf-rs) | Современные tools (parca-agent, tetragon, hubble, beyla) - все на libbpf+CO-RE. ## BTF на старых ядрах - BTFGen CO-RE требует BTF в kernel. Старые ядра (RHEL 7, Ubuntu 18.04) - `CONFIG_DEBUG_INFO_BTF` не было. Решение - **BTFGen** (от Aqua Security): генерится "минимальный" BTF под конкретное приложение, доставляется в его deploy. На target libbpf использует этот мини-BTF вместо vmlinux. Packaged в `libbpfgo/btfgen` или через `bpftool gen min_core_btf`. Для observability на legacy kernels - спасение. ## Миграция с BCC на libbpf+CO-RE Steps: 1. Скачать `vmlinux.h` от target kernel (или generate из своего): ``` bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h ``` 2. Заменить `#include ` на `#include "vmlinux.h"` 3. Заменить direct field access (`task->pid`) на `BPF_CORE_READ(task, pid)` 4. Userspace - переписать с Python BCC на C libbpf 5. Сгенерить skeleton, использовать `myprog__attach()` вместо BCC's `attach_kprobe` 6. Опубликовать как один static binary Утилита `bcc-to-libbpf` (community) делает базовое преобразование но не идеально - правки руками всё равно нужны. ## Когда CO-RE ломается - **`field 'foo' not found in target kernel`** - поле было переименовано/ удалено. Используй `bpf_core_field_exists` + fallback. - **`type 'struct foo' not found`** - struct переименована. Старые кейсы: `request_queue` → структура изменена в 6.x. Альтернатива - `BPF_CORE_READ_BITFIELD_PROBED` или прямой kprobe. - **kernel без BTF** (`/sys/kernel/btf/vmlinux` нет) - используй BTFGen или собирай custom kernel с `CONFIG_DEBUG_INFO_BTF=y`. - **Verifier rejected** - тот же verifier что и в [ebpf-basics](/kb/ebpf-basics.md), CO-RE сам по себе не помогает с safety checks. - **Crash после kernel-update** - вероятно тип, который ты читаешь, кардинально изменился (struct rebuilt). Re-test on new kernel, добавь field-existence check. ## Где почитать - https://nakryiko.com/posts/bpf-portability-and-co-re/ - оригинальная статья от Andrii Nakryiko (автора libbpf) - https://github.com/libbpf/libbpf-bootstrap - стартовый шаблон - https://docs.kernel.org/bpf/btf.html - спецификация BTF - https://ebpf.io/applications/ - real-world проекты на CO-RE ## Команды ```bash ls /sys/kernel/btf/ ``` Какие BTF доступны в kernel - vmlinux + все loaded modules ```bash bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h ``` Сгенерить vmlinux.h из BTF текущего kernel - один include для всех типов ```bash bpftool btf dump file myprog.bpf.o ``` Показать BTF из скомпилированной BPF-программы (для debug) ```bash clang -O2 -g -target bpf -c myprog.bpf.c -o myprog.bpf.o ``` Компиляция eBPF с -g (DWARF/BTF) - обязательно для CO-RE relocations ```bash bpftool gen skeleton myprog.bpf.o > myprog.skel.h ``` Сгенерить C skeleton из BPF-объекта - удобный API для userspace ```bash bpftool prog load myprog.bpf.o /sys/fs/bpf/myprog ``` Загрузить и pin программу - relocations происходят при load ```bash bpftool gen min_core_btf /sys/kernel/btf/vmlinux mini.btf prog1.bpf.o prog2.bpf.o ``` Сгенерить минимальный BTF для распространения на legacy kernels ```bash git clone --recursive https://github.com/libbpf/libbpf-bootstrap ``` Стартовый шаблон проекта на libbpf+CO-RE ## См. также - [eBPF - программируемый kernel](/kb/ebpf-basics.md) - [Kernel modules - LKM, modprobe, signing, DKMS](/kb/kernel-modules.md) - [eBPF XDP - kernel data-plane](/kb/ebpf-xdp.md) - [strace - какие syscall'ы делает процесс](/kb/cmd-strace.md) - [Linux namespaces](/kb/namespaces.md) - [Continuous profiling: Pyroscope, eBPF, flame graphs в проде](/kb/pyroscope-continuous-profiling.md)