🌐

eBPF - BCCチュートリアル 編

2022/03/09に公開

本記事は、eBPFにおけるライブラリであるBCC (BPF Compiler Collection)のチュートリアルです。インストール等の手順についてはUbuntu 20.04をターゲットとしています。以下、関連記事をいくつか書いていますので、必要に応じて参照して下さい。

BCC概要

BCC全体像

[1]

eBPFソースコードのコンパイラ

内部的にはClang/LLVMを利用しています。

実行時ライブラリ

BCCは、独自のライブラリであるlibbccを実行時にリンクします。他のプロジェクトで利用されているlibbpfは使用されておらず、libbccの中でeBPF関連システムコール(bpf_*)を利用しています。

インストール

$ sudo apt install bpfcc-tools linux-headers-$(uname -r)

フロントエンドの言語にC++を使う場合は以下をインストールしておきます。

$ sudo apt install libbpfcc-dev

上記以外にもPythonやC++の基本的なビルド&実行環境がで未インストールであれば、build-essential, cmake等のパッケージが必要になるかもしれません。

サンプルプログラム

サンプルのソースコードはGitHubにビルド&実行出来るように置いています。

eBPF自体のソースコードはC言語で書く必要がありますが、フロントエンドのソースコードはPyhon, C++, そしてluaを利用可能です。本記事ではPythonとC++を紹介します。なお、C++よりも情報量やコードの簡潔さの観点において(実行速度が問題にならない限り)、Pythonがおすすめです。

注意

BCC(libbcc)のAPIは、バージョンによって少しずつ変化しており、基本的に互換性がなさそうな変化を入れている様子です。

基本的な処理フロー

  1. eBPFのソースコードを書く
  2. BPFクラスのインスタンスに対して、eBPFのソースコードを渡す
  3. プローブ対象のイベントを指定し、そのイベントが発生したときに実行するeBPFの関数(コールバック)を指定する
  4. 結果をmap等から取り出して目的の処理を行う

C++

ヘッダファイルはbcc/BPF.hをインクルードする必要があります。

#include <bcc/BPF.h>

#include <fstream>
#include <iostream>
#include <string>

const std::string BPF_PROGRAM = R"(
int on_syscall_execve(void* ctx) {
  bpf_trace_printk("Hello world by execve call.\n");
  return 0;
}
)";

int main() {
  ebpf::BPF bpf;
  auto init_res = bpf.init(BPF_PROGRAM);
  if (init_res.code() != 0) {
    std::cerr << init_res.msg() << std::endl;
    return 1;
  }

  auto fnname = bpf.get_syscall_fnname("execve");
  auto attach_res = bpf.attach_kprobe(fnname, "on_syscall_execve");
  if (init_res.code() != 0) {
    std::cerr << attach_res.msg() << std::endl;
    return 1;
  }

  std::ifstream pipe("/sys/kernel/debug/tracing/trace_pipe");
  while (true) {
    std::string line;
    if (std::getline(pipe, line)) {
      std::cout << line << std::endl;
      auto detach_res = bpf.detach_kprobe(fnname);
      if (init_res.code() != 0) {
        std::cerr << detach_res.msg() << std::endl;
        return 1;
      }
      break;
    } else {
      std::cout << "Waiting for an event." << std::endl;
      sleep(1);
    }
  }

  return 0;
}

Pyhon

#!/usr/bin/python

from bcc import BPF

BPF_PROGRAM = """
int on_syscall_execve(void* ctx) {
  bpf_trace_printk("Hello world by execve call.\\n");
  return 0;
}
"""

bpf = BPF(text = BPF_PROGRAM)
fnname = bpf.get_syscall_fnname("execve")
bpf.attach_kprobe(event = fnname, fn_name = "on_syscall_execve")
bpf.trace_print()

コンパイル(C++のみ)

libbccをリンク(-lbccオプション)する必要があります。また、私の環境ではBCCのヘッダファイル(bcc/BPF.h等)は/usr/include/bccディレクトリにインストールされていましたので、自分で指定してパスを追加する必要はありませんでした。

CMakeLists.txtの例
cmake_minimum_required(VERSION 3.15)

set(CMAKE_CXX_STANDARD 17)
project("hello_world" LANGUAGES CXX)

find_package(PkgConfig)
pkg_check_modules(LIBBCC REQUIRED IMPORTED_TARGET libbcc)

set(BINARY_NAME hello_world)
add_executable(${BINARY_NAME}
  "src/main.cc"
)

target_include_directories(${BINARY_NAME} PRIVATE "src")
target_include_directories(${BINARY_NAME} PRIVATE PkgConfig::LIBBCC)
target_link_libraries(${BINARY_NAME} PRIVATE bcc)
ビルド
$ cd build
$ cmake ..

実行

管理者権限(sudoコマンドを使うなど)で実行する必要があります。

実行結果
        cpptools-23250   [000] d... 18353.039623: bpf_trace_printk: Hello world by execve call.
C++の場合
$ sudo ./hello_world
Pythonの場合
$ sudo python3 main.py

参考文献

https://github.com/iovisor/bcc/blob/master/INSTALL.md

https://blogs.oracle.com/linux/post/intro-to-bcc-2

脚注
  1. https://blogs.oracle.com/linux/post/intro-to-bcc-2から抜粋 ↩︎

Discussion