以下のステップで進む。
- uprobeの概要、使用法
- eBPFプログラムの作成
- ユーザ空間プログラムの作成
- コンパイル、動作確認
1. uprobeの概要、使用法
eBPFにはシステムコールや関数にフックする機能があるが、用途によって大きく2つに分かれる。
今回はprintf
にアタッチしたいのでuprobeを使うことになる。
さて、eBPFで関数をフックするには2つのプログラムが必要となる(これはkprobe/uprobe共通)。ここでは便宜上「eBPFプログラム」と「ユーザ空間プログラム」と呼ぶことにする。
- eBPFプログラム:フックした際の処理を記述するプログラム
- ユーザ空間プログラム:eBPFプログラムをカーネルにロードし、関数にアタッチするプログラム
これら2つのプログラムが揃って初めてフックが可能となることに注意。
2. eBPFプログラムの作成
ファイル名はhook.bpf.c
#include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> #include <bpf/bpf_core_read.h> SEC("uprobe//lib/x86_64-linux-gnu/libc.so.6:printf") int BPF_KPROBE(printf_hook, const char *str) { bpf_printk("hello, printf!: %s\n", str); return 0; } char LICENSE[] SEC("license") = "Dual BSD/GPL";
3. ユーザ空間プログラムの作成
ファイル名はhook.c
#include <stdio.h> #include <unistd.h> #include <bpf/libbpf.h> #include <bpf/bpf.h> #include "hook.skel.h" int main() { int err = 0; struct hook_bpf *skel = hook_bpf__open_and_load(); if (!skel) { printf("error: failed to open BPF object\n"); return 1; } LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, .func_name = "printf", .retprobe = false); skel->links.printf_hook = bpf_program__attach_uprobe_opts( skel->progs.printf_hook, -1, "/lib/x86_64-linux-gnu/libc.so.6", 0, &uprobe_opts); for (;;) { sleep(1); } hook_bpf__destroy(skel); return 0; }
4. コンパイル、動作確認
Makefile
TARGET = hook ARCH = $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/') BPF_OBJ = ${TARGET:=.bpf.o} USER_C = ${TARGET:=.c} USER_SKEL = ${TARGET:=.skel.h} all: $(TARGET) $(BPF_OBJ) .PHONY: all $(TARGET): $(USER_C) $(USER_SKEL) - rm -f $(TARGET).bpf-*.o.tmp - rm -f $(TARGET).bpf.o.temp-stream* gcc -Wall -o $(TARGET) $(USER_C) -L「ここにlibbpfのパスを入れる(例:-L/home/user/libbpf/src)」 -l:libbpf.a -lelf -lz %.bpf.o: %.bpf.c vmlinux.h clang \ -target bpf \ -D __TARGET_ARCH_$(ARCH) \ -I/usr/include/$(shell uname -m)-linux-gnu \ -Wall \ -O2 -g -o $@ -c $< llvm-strip -g $@ $(USER_SKEL): $(BPF_OBJ) bpftool gen skeleton $< > $@ vmlinux.h: bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h clean: - rm $(BPF_OBJ) - rm $(TARGET) - rm -f $(TARGET).bpf-*.o.tmp - rm -f $(TARGET).bpf.o.temp-stream*
テスト用のファイル(hello.c
)
#include <stdio.h> int main() { int tmp = 1; printf("hello! %d\n", tmp); printf("hello! %d\n", tmp); return 0; }
tmux
などで3画面開き、以下を順に行う。
- 1画面目は
sudo make
してからsudo ./hook
- 2画面目は
sudo cat /sys/kernel/tracing/trace_pipe
- 3画面目は
gcc -Wall hello.c
してから./a.out
a.out
を実行して、2画面目にhello, printf!: hello! %d
と出たらOK!