跳转至

Libbpf简介

libbpf 简介

libbpf是内核提供的一个功能库

eBPF程序的运行流程如图所示:

libbpf_ebpf_0

  • 生成eBPF程序字节码,字节码最终被内核中的「虚拟机」执行
  • 通过系统调用加载eBPF字节码到内核中,将eBPF程序attach到各个事件、函数
  • 创建map,用于在内核态与用户态进行数据交互
  • 「事件」、TP点触发时,调用attacheBPF字节码并执行其功能

示例

统计一段时间中syscall调用次数,项目文件结构如下:

  • syscall_count_kern.ceBPF程序的c代码,使用clang编译为eBPF字节码
  • syscall_count_user.cpp为用户态程序,用于调用libbpf加载eBPF字节码

eBPF 字节码生成

字节码生成有多种方式:

  • 手动编写字节码(手写汇编指令),使用libbpf中的内存加载模式
  • 编写c代码,由clang生成,使用libbpf解析elf文件获取相关字节码信息
  • 一些其他工具能够解析脚本语言生成字节码(bpftrace

使用clang进行编译,创建eBPF程序:

#include <asm/types.h>
#include <linux/types.h>

#include <bpf/bpf_helpers.h>
#include <linux/bpf.h>

// SEC 宏会将此字符串放到一个 elf 文件中的独立段里面供加载器检查
char LICENSE[] SEC("license") = "Dual BSD/GPL";

int my_pid = 0;

// 此处使用的 tracepoint,在每次进入 write 系统调用的时候触发
SEC("tp/syscalls/sys_enter_write")
int handle_tp(void *ctx) {
  int pid = bpf_get_current_pid_tgid() >> 32;

  if (pid != my_pid)
    return 0;
  // 可 cat /sys/kernel/debug/tracing/trace 查看调试信息
  bpf_printk("BPF triggered from PID %d.\n", pid);
  return 0;
}

生成 vmlinux.h 文件:

# 拷贝到工程目录下
bpftool btf dump file -/sys/kernel/btf/vmlinux format c > ./vmlinux.h

编译生成字节码(注意要使用-g -O2否则加载时会报错)

clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \
  -I/usr/src/kernels/$(uname -r)/include/uapi/ \
  -I/usr/src/kernels/$(uname -r)/include/ \
  -I/usr/include/bpf/ -c minimal.bpf.c -o minimal.bpf.o

转换为 skel

bpftool gen skeleton minimal.bpf.o > minimal.skel.h

libbpf 库使用

使用libbpf库加载eBPF程序代码如下:

#include "minimal.skel.h" // 这就是上一步生成的skeleton,minimal的“框架”
#include <bpf/libbpf.h>
#include <stdio.h>
#include <sys/resource.h> // rlimit使用
#include <unistd.h>

int main(int argc, char **argv) {
  struct minimal_bpf *skel; // bpftool生成到skel文件中,格式都是xxx_bpf。
  int err;

  struct rlimit rlim = {
      .rlim_cur = 512UL << 20,
      .rlim_max = 512UL << 20,
  };
  // bpf程序需要加载到lock memory中,因此需要将本进程的lock mem配大些
  if (setrlimit(RLIMIT_MEMLOCK, &rlim)) {
    fprintf(stderr, "set rlimit error!\n");
    return 1;
  }
  // 第一步,打开bpf文件,返回指向xxx_bpf的指针(在.skel中定义)
  skel = minimal_bpf__open();
  if (!skel) {
    fprintf(stderr, "Failed to open BPF skeleton\n");
    return 1;
  }

  // 第二步,加载及校验bpf程序
  err = minimal_bpf__load(skel);
  if (err) {
    fprintf(stderr, "Failed to load and verify BPF skeleton\n");
    goto cleanup;
  }

  // 第三步,附加到指定的hook点
  err = minimal_bpf__attach(skel);
  if (err) {
    fprintf(stderr, "Failed to attach BPF skeleton\n");
    goto cleanup;
  }

  printf("Successfully started! Please run `sudo cat "
         "/sys/kernel/debug/tracing/trace_pipe` "
         "to see output of the BPF programs.\n");

  for (;;) {
    /* trigger our BPF program */
    fprintf(stderr, ".");
    sleep(1);
  }
cleanup:
  minimal_bpf__destroy(skel);
  return -err;
}

编译用户态程序

gcc -I/usr/src/kernels/$(uname -r)/include/uapi/ \
  -I/usr/src/kernels/$(uname -r)/include/ \
  -I/usr/include/bpf/ -W -c minimal.c -o minimal.o

链接成为可执行程序

gcc minimal.o -lbpf -lelf -lz -o minimal