「車輪の再実装」って言葉が好き(実践はできてない)

Ubuntu18.04 でbcc, bpftraceを動かす

最近流行りのeBPFを研究でつかっている?のですが、せっかくなので使い方のメモを公開しておこうと思います。

OSのセットアップ

タイトルにもあるようにUbuntu18.04を使います。

bpfは最近流行っていることもあり、新しいバージョンのカーネルでないと使えない機能もあります。
せっかくなのでカーネルのバージョンを新しいものに上げておきましょう。
また、ヘッダーも必要なので、合わせてインストールしておきます。

sudo sed -i.bak -e "s%http://archive.ubuntu.com/ubuntu/%http://ftp.iij.ad.jp/pub/linux/ubuntu/archive/%g" /etc/apt/sources.list
sudo apt update
LATEST_KERNEL_IMAGE=$(apt search linux-image- 2>/dev/null |egrep  '^linux-image-[45].*-generic' |cut -d'/' -f1 | tail -n -1)
LATEST_KERNEL_HEADER=$(apt search linux-headers- 2>/dev/null |egrep  '^linux-headers-[45].*-generic' |cut -d'/' -f1 | tail -n -1)
sudo apt install -y $LATEST_KERNEL_HEADER $LATEST_KERNEL_IMAGE
sudo reboot

bcc

bccはbpf用のツールチェーンです。
特に、pythonモジュールを使うことで、かんたんにBPFのプログラムを動かすことができます。

Ubuntu 18.04では、aptを使えばbcc自体は入るのですが、
aptで入るbccはライブラリの依存の関係でbpftraceには使えないので、bcc自体も自分でビルドします。

基本的には公式のリポジトリの説明のとおりですが、python3で使いたいので cmakeの際に-DPYTHON_CMD=python3を足します。

$ cd ~
$ sudo apt-get -y install bison build-essential cmake flex git libedit-dev \
  libllvm6.0 llvm-6.0-dev libclang-6.0-dev python zlib1g-dev libelf-dev
$ git clone https://github.com/iovisor/bcc.git
$ mkdir bcc/build; cd bcc/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DPYTHON_CMD=python3
$ make -j4
$ sudo make install

これでインストールは完了です。

早速動かしてみましょう。

 cd ..
$ sudo python3 examples/hello_world.py

多分これだけだと何も表示されないはずです。

それもそのはず、このpythonのプログラムは以下のようにcloneが呼ばれたらHello, World!を出力する仕様になっています。

code = '''
int kprobe__sys_clone(void *ctx)
{
  bpf_trace_printk("Hello, World!\\n");
  return 0;
}
'''
BPF(text=code).trace_print()

というわけで、別端末等で適当にコマンドを実行してみましょう。
(シェルのbuiltinだとcloneが実行されない場合があるので、注意)

こんな感じで出力されるはずです。

b'            bash-1048  [000] ....  2985.061252: 0: Hello, World!'
b'            bash-1048  [000] ....  2987.416850: 0: Hello, World!'

余談ですが、python3ではなく2系で実行したらb' 'とは表示されませんでした。
python3対応が遅れてるみたいですね(涙)

なお、このブログを書いている時は Vagrantで試しているせいか引っかからなかったですが、
実機で試したときは

#  echo 1 > /proc/sys/kernel/sysrq
#  echo x > /proc/sysrq-trigger

としなければ動きませんでした。

このあたりの問題は FAQにまとめてあるので、 動かなかったら見てみると良いでしょう。

また、もしかすると、ライブラリを認識させるために、sudo ldconfig を実行する必要があるかもしれません。

bccの使い方についての説明は今回は省略します。

bpftrace

Dtrace や SystemTapのようなツールです。

カーネル内部にさくっとフックを仕込んでトレースしたい場合に便利なツールです。
カーネル内部だけでなく、ユーザー空間にフックを仕込むとう、幅広い使い方ができます。)

$ sudo apt update
$ sudo apt install -y bison cmake flex g++ git libelf-dev zlib1g-dev libfl-dev systemtap-sdt-dev \
                             llvm-7-dev llvm-7-runtime libclang-7-dev clang-7
$ git clone https://github.com/iovisor/bpftrace
$ mkdir bpftrace/build; cd bpftrace/build;
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ make -j4
$ sudo make install

さて、実行してみます。

(このプログラムは自分で終了する必要があります。数秒立ったらCtrl+Cで止めましょう。 実際の出力が出るまで少し時間がかかるかもしれません。)

sudo  bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'
Attaching 332 probes...
^C

@[tracepoint:syscalls:sys_enter_getsockname]: 1
@[tracepoint:syscalls:sys_enter_prctl]: 1
@[tracepoint:syscalls:sys_enter_adjtimex]: 1
@[tracepoint:syscalls:sys_enter_rename]: 1
@[tracepoint:syscalls:sys_enter_bind]: 1
@[tracepoint:syscalls:sys_enter_clone]: 1
@[tracepoint:syscalls:sys_enter_fchmod]: 1
@[tracepoint:syscalls:sys_enter_kill]: 1
@[tracepoint:syscalls:sys_enter_epoll_create1]: 1
@[tracepoint:syscalls:sys_enter_lseek]: 1
@[tracepoint:syscalls:sys_enter_exit_group]: 1
@[tracepoint:syscalls:sys_enter_rt_sigreturn]: 2
@[tracepoint:syscalls:sys_enter_getdents]: 2
@[tracepoint:syscalls:sys_enter_epoll_ctl]: 2
@[tracepoint:syscalls:sys_enter_socket]: 2
@[tracepoint:syscalls:sys_enter_write]: 3
@[tracepoint:syscalls:sys_enter_newlstat]: 3
@[tracepoint:syscalls:sys_enter_getpid]: 3
@[tracepoint:syscalls:sys_enter_getrandom]: 3
@[tracepoint:syscalls:sys_enter_readlinkat]: 3
@[tracepoint:syscalls:sys_enter_poll]: 4
@[tracepoint:syscalls:sys_enter_select]: 4
@[tracepoint:syscalls:sys_enter_futex]: 5
@[tracepoint:syscalls:sys_enter_munmap]: 5
@[tracepoint:syscalls:sys_enter_sendmsg]: 5
@[tracepoint:syscalls:sys_enter_inotify_add_watch]: 8
@[tracepoint:syscalls:sys_enter_access]: 9
@[tracepoint:syscalls:sys_enter_rt_sigprocmask]: 10
@[tracepoint:syscalls:sys_enter_recvmsg]: 11
@[tracepoint:syscalls:sys_enter_fcntl]: 12
@[tracepoint:syscalls:sys_enter_alarm]: 15
@[tracepoint:syscalls:sys_enter_rt_sigaction]: 20
@[tracepoint:syscalls:sys_enter_newfstat]: 30
@[tracepoint:syscalls:sys_enter_newstat]: 31
@[tracepoint:syscalls:sys_enter_epoll_wait]: 39
@[tracepoint:syscalls:sys_enter_read]: 148
@[tracepoint:syscalls:sys_enter_perf_event_open]: 150
@[tracepoint:syscalls:sys_enter_dup]: 302
@[tracepoint:syscalls:sys_enter_bpf]: 322
@[tracepoint:syscalls:sys_enter_openat]: 347
@[tracepoint:syscalls:sys_enter_ioctl]: 565
@[tracepoint:syscalls:sys_enter_dup2]: 602
@[tracepoint:syscalls:sys_enter_close]: 1029

ちゃんと動きました。

本記事はこれで終わり。
probeにも色々な種類があり、使い方に癖があったりするので、
その気になったらまた記事にするかもしれません。