iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0

如果覺得文章對你有所啟發,可以考慮用 🌟 支持 Gthulhu 專案,短期目標是集齊 300 個 🌟 藉此被 CNCF Landscape 採納 [ref]

前言

在前面的文章中,我們體驗了 eBPF 程式的強大功能,但您可能已經注意到一個問題:eBPF 程式在不同的核心版本或系統配置上可能無法正常工作。這是因為核心資料結構在不同版本間可能發生變化,導致程式相容性問題。

今天我們將深入學習 CO-RE (Compile Once, Run Everywhere) 技術,這是解決 eBPF 程式可移植性問題的關鍵技術。通過 CO-RE,我們可以編寫一次 eBPF 程式,然後在任何支援的核心版本上運行。

筆者補充:
在 CO-RE 出現之前,我們可以使用 BCC 動態的載入 eBPF program 以解決 portability 的問題。但是對於 container-based service 來說,BCC 依賴的 tool chain 實在太大太雜了。因此,eBPF 社群就透過 relocation 的方式使 eBPF 能夠做到 Compile Once, Run Everywhere!但是使用 CO-RE 仍須注意 helper function、kfuncs 以及不同 program type 在跨版本支援度的問題唷。

什麼是 CO-RE?

核心概念

CO-RE 是 eBPF 生態系統中的一項革命性技術,它包含三個核心組件:

  1. BTF (BPF Type Format):核心資料結構的元資料描述
  2. Libbpf:使用者空間的重定位庫
  3. Compiler Built-ins:編譯器內建的幫助函數

傳統問題

在 CO-RE 之前,eBPF 程式面臨以下挑戰:

// 傳統方式:硬編碼偏移量
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
pid_t pid = *(pid_t *)((char *)task + 1248); // 偏移量在不同核心版本間可能改變

這種方式的問題:

  • 偏移量隨核心版本變化
  • 需要為每個核心版本重新編譯
  • 維護成本極高

CO-RE 解決方案

// CO-RE 方式:使用 BPF_CORE_READ
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
pid_t pid = BPF_CORE_READ(task, pid); // 自動處理不同核心版本的差異

CO-RE 的優勢:

  • 編譯一次,到處運行
  • 自動處理核心版本差異
  • 程式碼更簡潔易讀

BTF 詳解

BTF 格式

BTF 是一種緊湊的元資料格式,描述了 C 資料類型:

# 查看核心 BTF 資訊
bpftool btf dump file /sys/kernel/btf/vmlinux format c

# 查看特定結構體
bpftool btf dump file /sys/kernel/btf/vmlinux | grep "struct task_struct"

BTF 生成

# 從核心 DWARF 資訊生成 BTF
pahole --btf_encode_detached vmlinux vmlinux.btf

# 檢查 BTF 格式
bpftool btf dump file vmlinux.btf format raw

BTF 結構示例

// BTF 描述的 task_struct 結構
[1] STRUCT 'task_struct' size=8704 vlen=207
    'state' type_id=2 bits_offset=0
    'stack' type_id=3 bits_offset=64
    'usage' type_id=4 bits_offset=128
    'flags' type_id=5 bits_offset=160
    'ptrace' type_id=5 bits_offset=192
    'on_cpu' type_id=6 bits_offset=224
    'wake_entry' type_id=7 bits_offset=256
    'cpu' type_id=8 bits_offset=352
    'recent_used_cpu' type_id=8 bits_offset=384
    'pid' type_id=9 bits_offset=1056  // pid 在位置 1056
    ...

CO-RE 重定位機制

重定位類型

CO-RE 支持多種重定位類型:

  1. Field Offset:欄位偏移量
  2. Field Size:欄位大小
  3. Field Existence:欄位是否存在
  4. Type Size:類型大小
  5. Enum Value:列舉值

重定位範例

建立 core_example.bpf.c

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

// 傳統方式 vs CO-RE 方式比較

SEC("tp/sched/sched_process_exec")
int trace_exec_traditional(struct trace_event_raw_sched_process_exec *ctx)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // 傳統方式:硬編碼偏移量(危險!)
    // pid_t pid = *(pid_t *)((char *)task + 1056);
    
    return 0;
}

SEC("tp/sched/sched_process_exec")
int trace_exec_core(struct trace_event_raw_sched_process_exec *ctx)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // CO-RE 方式:自動重定位
    pid_t pid = BPF_CORE_READ(task, pid);
    
    // 檢查欄位是否存在
    if (bpf_core_field_exists(task->pid)) {
        bpf_printk("PID field exists: %d", pid);
    }
    
    // 取得欄位大小
    size_t pid_size = bpf_core_field_size(task->pid);
    bpf_printk("PID field size: %lu", pid_size);
    
    return 0;
}

char _license[] SEC("license") = "GPL";

CO-RE 巨集詳解

BPF_CORE_READ 家族

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

SEC("tp/syscalls/sys_enter_openat")
int trace_openat(struct trace_event_raw_sys_enter *ctx)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // 1. BPF_CORE_READ - 讀取單一欄位
    pid_t pid = BPF_CORE_READ(task, pid);
    pid_t tgid = BPF_CORE_READ(task, tgid);
    
    // 2. BPF_CORE_READ_STR - 讀取字串
    char comm[16];
    BPF_CORE_READ_STR(comm, sizeof(comm), task, comm);
    
    // 3. BPF_CORE_READ_INTO - 讀取到指定變數
    pid_t local_pid;
    BPF_CORE_READ_INTO(&local_pid, task, pid);
    
    // 4. BPF_CORE_READ_USER - 讀取使用者空間資料
    char __user *filename = (char __user *)ctx->args[1];
    char filename_buf[256];
    BPF_CORE_READ_USER_STR(filename_buf, sizeof(filename_buf), filename);
    
    bpf_printk("Process %s (PID: %d, TGID: %d) opening: %s", 
               comm, pid, tgid, filename_buf);
    
    return 0;
}

鏈式讀取

SEC("tp/sched/sched_process_fork")
int trace_fork(struct trace_event_raw_sched_process_fork *ctx)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // 鏈式讀取:task->mm->mmap->vm_file->f_path->dentry->d_name->name
    char *exe_name = BPF_CORE_READ(task, mm, exe_file, f_path.dentry, d_name.name);
    
    // 多層指標讀取
    struct mm_struct *mm = BPF_CORE_READ(task, mm);
    if (mm) {
        unsigned long start_brk = BPF_CORE_READ(mm, start_brk);
        unsigned long brk = BPF_CORE_READ(mm, brk);
        
        bpf_printk("Process heap: start=0x%lx, current=0x%lx", start_brk, brk);
    }
    
    return 0;
}

條件編譯與相容性

SEC("tp/sched/sched_process_exit")
int trace_exit(struct trace_event_raw_sched_process_exit *ctx)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // 檢查欄位是否存在
    if (bpf_core_field_exists(task->exit_code)) {
        int exit_code = BPF_CORE_READ(task, exit_code);
        bpf_printk("Process exit with code: %d", exit_code);
    } else {
        bpf_printk("exit_code field not available in this kernel");
    }
    
    // 取得類型大小
    size_t task_size = bpf_core_type_size(struct task_struct);
    bpf_printk("task_struct size: %lu bytes", task_size);
    
    // 列舉值處理
    if (bpf_core_enum_value_exists(enum pid_type, PIDTYPE_PID)) {
        bpf_printk("PIDTYPE_PID value: %d", 
                   bpf_core_enum_value(enum pid_type, PIDTYPE_PID));
    }
    
    return 0;
}

實戰範例:跨版本相容的程序監控器

程式結構設計

建立 process_monitor.h

#ifndef __PROCESS_MONITOR_H__
#define __PROCESS_MONITOR_H__

#include <linux/types.h>

#define TASK_COMM_LEN 16
#define MAX_PATH_LEN 256

// 程序事件類型
enum event_type {
    EVENT_EXEC = 1,
    EVENT_EXIT,
    EVENT_FORK,
    EVENT_CLONE,
};

// 程序事件結構
struct process_event {
    __u64 timestamp;
    __u32 event_type;
    __u32 pid;
    __u32 ppid;
    __u32 tid;
    __u32 uid;
    __u32 gid;
    __s32 exit_code;
    char comm[TASK_COMM_LEN];
    char filename[MAX_PATH_LEN];
} __attribute__((packed));

// 程序統計
struct process_stats {
    __u64 total_events;
    __u64 exec_count;
    __u64 exit_count;
    __u64 fork_count;
    __u64 clone_count;
} __attribute__((packed));

#endif /* __PROCESS_MONITOR_H__ */

eBPF 程式實作

建立 process_monitor.bpf.c

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_tracing.h>
#include "process_monitor.h"

// Ring Buffer for events
struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024);
} events SEC(".maps");

// Statistics map
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 1);
    __type(key, __u32);
    __type(value, struct process_stats);
} stats_map SEC(".maps");

// 更新統計資訊
static __always_inline void update_stats(enum event_type type)
{
    __u32 key = 0;
    struct process_stats *stats = bpf_map_lookup_elem(&stats_map, &key);
    if (!stats) {
        return;
    }
    
    __sync_fetch_and_add(&stats->total_events, 1);
    
    switch (type) {
        case EVENT_EXEC:
            __sync_fetch_and_add(&stats->exec_count, 1);
            break;
        case EVENT_EXIT:
            __sync_fetch_and_add(&stats->exit_count, 1);
            break;
        case EVENT_FORK:
            __sync_fetch_and_add(&stats->fork_count, 1);
            break;
        case EVENT_CLONE:
            __sync_fetch_and_add(&stats->clone_count, 1);
            break;
    }
}

// 填充基本程序資訊
static __always_inline void fill_process_info(struct process_event *event)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    event->timestamp = bpf_ktime_get_ns();
    
    // 使用 CO-RE 安全讀取
    event->pid = BPF_CORE_READ(task, pid);
    event->tid = BPF_CORE_READ(task, tgid);
    
    // 讀取父程序 PID
    struct task_struct *parent = BPF_CORE_READ(task, parent);
    if (parent) {
        event->ppid = BPF_CORE_READ(parent, pid);
    }
    
    // 讀取使用者資訊
    kuid_t uid = BPF_CORE_READ(task, cred, uid);
    kgid_t gid = BPF_CORE_READ(task, cred, gid);
    event->uid = uid.val;
    event->gid = gid.val;
    
    // 讀取程序名稱
    BPF_CORE_READ_STR(event->comm, sizeof(event->comm), task, comm);
}

// 取得執行檔路徑
static __always_inline int get_exe_path(char *buf, size_t size)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // 檢查 mm 是否存在
    if (!bpf_core_field_exists(task->mm) || !BPF_CORE_READ(task, mm)) {
        return -1;
    }
    
    struct mm_struct *mm = BPF_CORE_READ(task, mm);
    if (!mm) {
        return -1;
    }
    
    // 檢查 exe_file 是否存在
    if (!bpf_core_field_exists(mm->exe_file)) {
        return -1;
    }
    
    struct file *exe_file = BPF_CORE_READ(mm, exe_file);
    if (!exe_file) {
        return -1;
    }
    
    // 讀取檔案路徑
    struct path path = BPF_CORE_READ(exe_file, f_path);
    struct dentry *dentry = BPF_CORE_READ(&path, dentry);
    
    if (dentry) {
        struct qstr d_name = BPF_CORE_READ(dentry, d_name);
        BPF_CORE_READ_STR(buf, size, d_name.name);
        return 0;
    }
    
    return -1;
}

// 程序執行事件
SEC("tp/sched/sched_process_exec")
int trace_exec(struct trace_event_raw_sched_process_exec *ctx)
{
    struct process_event *event;
    
    // 預留 Ring Buffer 空間
    event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
    if (!event) {
        return 0;
    }
    
    // 初始化事件
    __builtin_memset(event, 0, sizeof(*event));
    event->event_type = EVENT_EXEC;
    
    // 填充基本資訊
    fill_process_info(event);
    
    // 取得執行檔路徑
    if (get_exe_path(event->filename, sizeof(event->filename)) != 0) {
        // 如果無法取得路徑,使用程序名稱
        __builtin_memcpy(event->filename, event->comm, sizeof(event->comm));
    }
    
    // 提交事件
    bpf_ringbuf_submit(event, 0);
    
    // 更新統計
    update_stats(EVENT_EXEC);
    
    return 0;
}

// 程序結束事件
SEC("tp/sched/sched_process_exit")
int trace_exit(struct trace_event_raw_sched_process_exit *ctx)
{
    struct process_event *event;
    
    event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
    if (!event) {
        return 0;
    }
    
    __builtin_memset(event, 0, sizeof(*event));
    event->event_type = EVENT_EXIT;
    
    fill_process_info(event);
    
    // 讀取退出代碼
    if (bpf_core_field_exists(((struct task_struct *)0)->exit_code)) {
        struct task_struct *task = (struct task_struct *)bpf_get_current_task();
        event->exit_code = BPF_CORE_READ(task, exit_code);
    }
    
    bpf_ringbuf_submit(event, 0);
    update_stats(EVENT_EXIT);
    
    return 0;
}

// 程序 fork 事件
SEC("tp/sched/sched_process_fork")
int trace_fork(struct trace_event_raw_sched_process_fork *ctx)
{
    struct process_event *event;
    
    event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
    if (!event) {
        return 0;
    }
    
    __builtin_memset(event, 0, sizeof(*event));
    event->event_type = EVENT_FORK;
    
    fill_process_info(event);
    
    bpf_ringbuf_submit(event, 0);
    update_stats(EVENT_FORK);
    
    return 0;
}

// kprobe 範例:監控 do_exit
SEC("kprobe/do_exit")
int kprobe_do_exit(struct pt_regs *ctx)
{
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    pid_t pid = BPF_CORE_READ(task, pid);
    pid_t tgid = BPF_CORE_READ(task, tgid);
    
    // 取得退出代碼參數
    long exit_code = PT_REGS_PARM1(ctx);
    
    bpf_printk("Process %d (tgid: %d) exiting with code: %ld", 
               pid, tgid, exit_code);
    
    return 0;
}

char _license[] SEC("license") = "GPL";

Go 程式實作

建立 main.go

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
    "unsafe"

    "github.com/aquasecurity/libbpfgo"
)

// 對應 C 結構體
type ProcessEvent struct {
    Timestamp uint64
    EventType uint32
    PID       uint32
    PPID      uint32
    TID       uint32
    UID       uint32
    GID       uint32
    ExitCode  int32
    Comm      [16]byte
    Filename  [256]byte
}

type ProcessStats struct {
    TotalEvents uint64
    ExecCount   uint64
    ExitCount   uint64
    ForkCount   uint64
    CloneCount  uint64
}

// 事件類型常數
const (
    EventExec = 1
    EventExit = 2
    EventFork = 3
    EventClone = 4
)

type ProcessMonitor struct {
    module *libbpfgo.Module
    rb     *libbpfgo.RingBuffer
}

func NewProcessMonitor(objPath string) (*ProcessMonitor, error) {
    // 載入 eBPF 程式
    module, err := libbpfgo.NewModuleFromFile(objPath)
    if err != nil {
        return nil, fmt.Errorf("failed to load BPF object: %v", err)
    }

    if err := module.BPFLoadObject(); err != nil {
        return nil, fmt.Errorf("failed to load BPF object: %v", err)
    }

    // 附加 tracepoint 程式
    execProg, err := module.GetProgram("trace_exec")
    if err != nil {
        return nil, fmt.Errorf("failed to get exec program: %v", err)
    }
    
    exitProg, err := module.GetProgram("trace_exit")
    if err != nil {
        return nil, fmt.Errorf("failed to get exit program: %v", err)
    }
    
    forkProg, err := module.GetProgram("trace_fork")
    if err != nil {
        return nil, fmt.Errorf("failed to get fork program: %v", err)
    }

    kprobeProg, err := module.GetProgram("kprobe_do_exit")
    if err != nil {
        return nil, fmt.Errorf("failed to get kprobe program: %v", err)
    }

    // 附加程式
    if _, err := execProg.AttachTracepoint("sched", "sched_process_exec"); err != nil {
        return nil, fmt.Errorf("failed to attach exec tracepoint: %v", err)
    }
    
    if _, err := exitProg.AttachTracepoint("sched", "sched_process_exit"); err != nil {
        return nil, fmt.Errorf("failed to attach exit tracepoint: %v", err)
    }
    
    if _, err := forkProg.AttachTracepoint("sched", "sched_process_fork"); err != nil {
        return nil, fmt.Errorf("failed to attach fork tracepoint: %v", err)
    }

    if _, err := kprobeProg.AttachKprobe("do_exit"); err != nil {
        log.Printf("Warning: failed to attach kprobe: %v", err)
    }

    return &ProcessMonitor{
        module: module,
    }, nil
}

func (pm *ProcessMonitor) StartMonitoring() error {
    // 取得 Ring Buffer
    eventsMap, err := pm.module.GetMap("events")
    if err != nil {
        return fmt.Errorf("failed to get events map: %v", err)
    }

    pm.rb, err = pm.module.InitRingBuf("events", pm.handleEvent)
    if err != nil {
        return fmt.Errorf("failed to init ring buffer: %v", err)
    }

    // 開始處理事件
    pm.rb.Start()
    return nil
}

func (pm *ProcessMonitor) handleEvent(data []byte) {
    // 解析事件
    event := (*ProcessEvent)(unsafe.Pointer(&data[0]))
    
    // 轉換字串
    comm := nullTerminatedString(event.Comm[:])
    filename := nullTerminatedString(event.Filename[:])
    
    // 格式化時間
    timestamp := time.Unix(0, int64(event.Timestamp))
    
    // 格式化事件類型
    eventTypeStr := map[uint32]string{
        EventExec:  "EXEC",
        EventExit:  "EXIT",
        EventFork:  "FORK",
        EventClone: "CLONE",
    }[event.EventType]
    
    // 輸出事件
    fmt.Printf("[%s] %s: PID=%d PPID=%d TID=%d UID=%d GID=%d",
        timestamp.Format("15:04:05.000"),
        eventTypeStr,
        event.PID,
        event.PPID,
        event.TID,
        event.UID,
        event.GID)
    
    if event.EventType == EventExit {
        fmt.Printf(" ExitCode=%d", event.ExitCode)
    }
    
    fmt.Printf(" Comm=%s", comm)
    
    if filename != "" && filename != comm {
        fmt.Printf(" File=%s", filename)
    }
    
    fmt.Println()
}

func (pm *ProcessMonitor) GetStats() (*ProcessStats, error) {
    statsMap, err := pm.module.GetMap("stats_map")
    if err != nil {
        return nil, fmt.Errorf("failed to get stats map: %v", err)
    }

    key := uint32(0)
    value, err := statsMap.GetValue(unsafe.Pointer(&key))
    if err != nil {
        return nil, fmt.Errorf("failed to get stats: %v", err)
    }

    // 處理 Per-CPU 統計
    stats := &ProcessStats{}
    cpuCount := len(value) / int(unsafe.Sizeof(*stats))
    
    for i := 0; i < cpuCount; i++ {
        offset := i * int(unsafe.Sizeof(*stats))
        cpuStats := (*ProcessStats)(unsafe.Pointer(&value[offset]))
        
        stats.TotalEvents += cpuStats.TotalEvents
        stats.ExecCount += cpuStats.ExecCount
        stats.ExitCount += cpuStats.ExitCount
        stats.ForkCount += cpuStats.ForkCount
        stats.CloneCount += cpuStats.CloneCount
    }

    return stats, nil
}

func (pm *ProcessMonitor) PrintStats() {
    stats, err := pm.GetStats()
    if err != nil {
        log.Printf("Failed to get stats: %v", err)
        return
    }

    fmt.Println("\n=== Process Monitor Statistics ===")
    fmt.Printf("Total Events: %d\n", stats.TotalEvents)
    fmt.Printf("Exec Events:  %d\n", stats.ExecCount)
    fmt.Printf("Exit Events:  %d\n", stats.ExitCount)
    fmt.Printf("Fork Events:  %d\n", stats.ForkCount)
    fmt.Printf("Clone Events: %d\n", stats.CloneCount)
}

func (pm *ProcessMonitor) Close() {
    if pm.rb != nil {
        pm.rb.Stop()
        pm.rb.Close()
    }
    if pm.module != nil {
        pm.module.Close()
    }
}

func nullTerminatedString(b []byte) string {
    if i := bytes.IndexByte(b, 0); i >= 0 {
        return string(b[:i])
    }
    return string(b)
}

func main() {
    // 建立程序監控器
    monitor, err := NewProcessMonitor("process_monitor.bpf.o")
    if err != nil {
        log.Fatalf("Failed to create process monitor: %v", err)
    }
    defer monitor.Close()

    // 開始監控
    if err := monitor.StartMonitoring(); err != nil {
        log.Fatalf("Failed to start monitoring: %v", err)
    }

    fmt.Println("Process Monitor started. Press Ctrl+C to stop...")
    fmt.Println("Monitoring process exec, exit, and fork events...")

    // 設定信號處理
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

    // 定期顯示統計資訊
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            monitor.PrintStats()
        case <-sigChan:
            fmt.Println("\nShutting down...")
            monitor.PrintStats()
            return
        }
    }
}

測試與驗證

編譯和執行

建立 Makefile

# Process Monitor Makefile

CLANG ?= clang
LLVM_STRIP ?= llvm_strip
ARCH := x86_64

# 輸出檔案
BPF_OBJ = process_monitor.bpf.o
TARGET = process_monitor

# 編譯標誌
CFLAGS := -O2 -g -Wall -Werror
BPF_CFLAGS := -target bpf -D__TARGET_ARCH_$(ARCH)

# 包含路徑
INCLUDES := -I/usr/include/$(shell uname -m)-linux-gnu -I. -I./vmlinux

.PHONY: all clean test vmlinux

all: vmlinux $(BPF_OBJ) $(TARGET)

# 生成 vmlinux.h
vmlinux:
	mkdir -p vmlinux
	bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux/vmlinux.h

# 編譯 eBPF 程式
$(BPF_OBJ): process_monitor.bpf.c process_monitor.h
	$(CLANG) $(BPF_CFLAGS) $(INCLUDES) $(CFLAGS) -c $< -o $@
	$(LLVM_STRIP) -g $@

# 編譯 Go 程式
$(TARGET): main.go $(BPF_OBJ)
	go mod init process_monitor || true
	go get github.com/aquasecurity/libbpfgo
	go build -o $(TARGET) main.go

# 測試
test: $(TARGET)
	@echo "Testing process monitor..."
	@echo "Run: sudo ./$(TARGET)"
	@echo "In another terminal, run some commands to generate events"

# 檢查 BTF 支援
check-btf:
	@if [ ! -f /sys/kernel/btf/vmlinux ]; then \
		echo "ERROR: BTF not supported on this kernel"; \
		echo "Enable CONFIG_DEBUG_INFO_BTF=y in kernel config"; \
		exit 1; \
	fi
	@echo "BTF support: OK"

# 清理
clean:
	rm -f $(BPF_OBJ) $(TARGET)
	rm -rf vmlinux/
	rm -f go.mod go.sum

help:
	@echo "Available targets:"
	@echo "  all       - Build all components"
	@echo "  vmlinux   - Generate vmlinux.h"
	@echo "  test      - Run test"
	@echo "  check-btf - Check BTF support"
	@echo "  clean     - Clean all artifacts"
	@echo "  help      - Show this help"

測試執行

# 檢查 BTF 支援
make check-btf

# 編譯程式
make all

# 執行監控器
sudo ./process_monitor

測試案例

在另一個終端執行:

# 產生不同類型的事件
ls /tmp                    # exec 事件
sleep 1                    # exec + exit 事件
bash -c "echo hello"       # fork + exec + exit 事件
python3 -c "print('hi')"   # exec + exit 事件

CO-RE 最佳實踐

1. 錯誤處理

// 總是檢查欄位是否存在
if (bpf_core_field_exists(task->some_field)) {
    value = BPF_CORE_READ(task, some_field);
} else {
    // 提供備選方案或跳過
    bpf_printk("Field not available in this kernel version");
}

2. 向後相容性

// 使用條件編譯處理不同核心版本
#if __has_builtin(__builtin_preserve_access_index)
    #define BPF_CORE_READ_BITFIELD_PROBED(dst, src) \
        __builtin_preserve_access_index(({dst = src;}))
#else
    #define BPF_CORE_READ_BITFIELD_PROBED(dst, src) \
        bpf_probe_read(&dst, sizeof(dst), &src)
#endif

3. 效能最佳化

// 快取常用的資料結構大小
static const size_t task_struct_size = bpf_core_type_size(struct task_struct);

// 使用編譯時常數
#define PID_OFFSET bpf_core_field_offset(struct task_struct, pid)

4. 除錯技巧

// 除錯資訊
#ifdef DEBUG
    bpf_printk("Field offset: %d, size: %d", 
               bpf_core_field_offset(struct task_struct, pid),
               bpf_core_field_size(struct task_struct, pid));
#endif

常見問題與解決方案

1. BTF 不可用

# 檢查 BTF 支援
ls /sys/kernel/btf/vmlinux

# 如果不存在,檢查核心配置
zcat /proc/config.gz | grep BTF

2. 編譯錯誤

// 確保包含正確的標頭檔
#include "vmlinux.h"  // 必須在其他 include 之前
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

3. 重定位失敗

# 檢查 BTF 資訊
bpftool btf dump file /sys/kernel/btf/vmlinux | grep "struct task_struct"

# 檢查程式載入資訊
bpftool prog load process_monitor.bpf.o /sys/fs/bpf/test

進階技巧

1. 自定義重定位

// 自定義存取巨集
#define CORE_READ_TASK_FIELD(task, field) ({ \
    typeof(((struct task_struct *)0)->field) __val; \
    BPF_CORE_READ_INTO(&__val, task, field); \
    __val; \
})

2. 多架構支援

// 架構特定的處理
#if defined(__TARGET_ARCH_x86)
    #define ARCH_SPECIFIC_OFFSET 8
#elif defined(__TARGET_ARCH_arm64)
    #define ARCH_SPECIFIC_OFFSET 16
#else
    #define ARCH_SPECIFIC_OFFSET 0
#endif

3. 版本檢查

// 核心版本檢查
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,8,0)
    // 新版本特性
#else
    // 舊版本相容性處理
#endif

總結

通過本篇文章,我們深入學習了 CO-RE 技術:

  1. 理解了 CO-RE 的核心概念:BTF、Libbpf、編譯器支援
  2. 掌握了 BPF_CORE_READ 家族巨集:安全讀取核心資料結構
  3. 學會了處理相容性問題:欄位存在檢查、條件編譯
  4. 實作了完整的監控程式:跨版本相容的程序監控器
  5. 了解了最佳實踐:錯誤處理、效能最佳化、除錯技巧

CO-RE 技術是現代 eBPF 開發的基石,讓我們能夠編寫真正可移植的 eBPF 程式。


上一篇
實戰:打造 Tiny Load Balancer
下一篇
內核追蹤與監控
系列文
30 篇文帶你用 eBPF 與 Golang 打造 Linux Scheduler12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言