eBPF - BCCチュートリアル 編
本記事は、eBPFにおけるライブラリであるBCC (BPF Compiler Collection)
のチュートリアルです。インストール等の手順についてはUbuntu 20.04
をターゲットとしています。以下、関連記事をいくつか書いていますので、必要に応じて参照して下さい。
- eBPF - 入門概要 編
- eBPF - 仮想マシン 編
- eBPF - BCCチュートリアル 編(本記事)
- eBPF - bpftraceチュートリアル 編
- eBPF - XDP概要 編
BCC概要
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は、バージョンによって少しずつ変化しており、基本的に互換性がなさそうな変化を入れている様子です。
基本的な処理フロー
- eBPFのソースコードを書く
- BPFクラスのインスタンスに対して、eBPFのソースコードを渡す
- プローブ対象のイベントを指定し、そのイベントが発生したときに実行するeBPFの関数(コールバック)を指定する
- 結果を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
ディレクトリにインストールされていましたので、自分で指定してパスを追加する必要はありませんでした。
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.
$ sudo ./hello_world
$ sudo python3 main.py
参考文献
Discussion