前回→eBPFについて学ぶ② - eBPFマップ
次回→eBPFについて学ぶ④ - eBPF検証器
(https://qiita.com/yamada3/items/f16f7f0fc54b9d7a338a)
前回の続きなので、参照するとわかりやすいです。
eBPF Beginner Skill Pathを日本語訳しながら再度まとめているため、同時にサイトを見ながら進めるとわかりやすいです。
公式GitHubリポジトリ:GitHub - dorkamotorka/ebpf-hello-world: eBPF Hello World Program using ebpf-go framework
シリーズ
#1→eBPFについて学ぶ① - 基本
#2→eBPFについて学ぶ② - eBPFマップ
#3→[いまここ]
#4→eBPFについて学ぶ④ - eBPF検証器
eBPFツールの「bpftool」と「bpftop」を使用し、eBPFプログラムを検査・分析・監視を行い、カーネル内部での動作やパフォーマンスを可視化する。
ざっくり
bpftool: カーネル内のeBPFプログラムとマップを検査する
bpftop: eBPFプログラムをリアルタイムでモニタリングする
前回からhello.c, main.goに変更はありません。同様にアプリケーションを構築・実行します
$ go generate
$ go build
$ sudo ./lab3
bpftool
プログラムとマップがカーネルに正しくロードされているか、プログラムがいつ、誰によってロードされたかを確認できる。また、マップの内部状態を可視化できる。
eBPFプログラムのリストと検査
以下のコマンドで、カーネルに現在ロードされているすべてのeBPFプログラムを表示できる。
$ sudo bpftool prog list
出力は以下の通り
15: cgroup_skb name sd_fw_ingress tag 6deef7357e7b4530 gpl
loaded_at 2026-02-24T00:01:01+0000 uid 0
xlated 64B jited 58B memlock 4096B
17: tracepoint name handle_execve_tp tag 8236b54ceef5a3ce gpl
loaded_at 2026-02-24T00:04:25+0000 uid 0
xlated 560B jited 379B memlock 4096B map_ids 6,8
btf_id 28
※loadedとattachedの違い
loaded: プログラムが検証済みでカーネルに受け入れられたが、アクティブではない。
attached: プログラムがアクティブで、特定のフックに紐づいている。
eBPFプログラムには一意のidが割り当てられている。このidを利用して詳細を表示できる。
以下を実行すると、eBPFプログラムの詳細を表示できる。
$ sudo bpftool prog show id 17 --pretty
出力は以下の通り
{
"id": 17,
"type": "tracepoint",
"name": "handle_execve_tp",
"tag": "8236b54ceef5a3ce",
"gpl_compatible": true,
"loaded_at": 1771891465,
"uid": 0,
"orphaned": false,
"bytes_xlated": 560,
"jited": true,
"bytes_jited": 379,
"bytes_memlock": 4096,
"map_ids": [6,8
],
"btf_id": 28
}
カーネルにロードされたeBPFバイトコードを検査することもできる。
$ sudo bpftool prog dump xlated id 17
出力は以下の通り
int handle_execve_tp(struct trace_event_raw_sys_enter * ctx):
; const char *filename = (const char *)ctx->args[0];
0: (79) r3 = *(u64 *)(r1 +16)
1: (b7) r1 = 0
; struct path_key key = {};
2: (7b) *(u64 *)(r10 -8) = r1
3: (7b) *(u64 *)(r10 -16) = r1
4: (7b) *(u64 *)(r10 -24) = r1
5: (7b) *(u64 *)(r10 -32) = r1
6: (7b) *(u64 *)(r10 -40) = r1
7: (7b) *(u64 *)(r10 -48) = r1
8: (7b) *(u64 *)(r10 -56) = r1
9: (7b) *(u64 *)(r10 -64) = r1
10: (7b) *(u64 *)(r10 -72) = r1
11: (7b) *(u64 *)(r10 -80) = r1
12: (7b) *(u64 *)(r10 -88) = r1
13: (7b) *(u64 *)(r10 -96) = r1
14: (7b) *(u64 *)(r10 -104) = r1
15: (7b) *(u64 *)(r10 -112) = r1
16: (7b) *(u64 *)(r10 -120) = r1
17: (7b) *(u64 *)(r10 -128) = r1
18: (7b) *(u64 *)(r10 -136) = r1
19: (7b) *(u64 *)(r10 -144) = r1
20: (7b) *(u64 *)(r10 -152) = r1
21: (7b) *(u64 *)(r10 -160) = r1
22: (7b) *(u64 *)(r10 -168) = r1
23: (7b) *(u64 *)(r10 -176) = r1
24: (7b) *(u64 *)(r10 -184) = r1
25: (7b) *(u64 *)(r10 -192) = r1
26: (7b) *(u64 *)(r10 -200) = r1
27: (7b) *(u64 *)(r10 -208) = r1
28: (7b) *(u64 *)(r10 -216) = r1
29: (7b) *(u64 *)(r10 -224) = r1
30: (7b) *(u64 *)(r10 -232) = r1
31: (7b) *(u64 *)(r10 -240) = r1
32: (7b) *(u64 *)(r10 -248) = r1
33: (7b) *(u64 *)(r10 -256) = r1
34: (bf) r1 = r10
;
35: (07) r1 += -256
; long n = bpf_probe_read_user_str(key.path, sizeof(key.path), filename);
36: (b7) r2 = 256
37: (85) call bpf_probe_read_user_str#-72624
38: (b7) r1 = 1
; if (n <= 0) {
39: (6d) if r1 s> r0 goto pc+28
40: (bf) r2 = r10
; __u64 *val = bpf_map_lookup_elem(&exec_count, &key);
41: (07) r2 += -256
42: (18) r1 = map[id:6]
44: (85) call __htab_map_lookup_elem#191824
45: (15) if r0 == 0x0 goto pc+1
46: (07) r0 += 304
; if (val) {
47: (15) if r0 == 0x0 goto pc+4
; *val += 1;
48: (79) r1 = *(u64 *)(r0 +0)
49: (07) r1 += 1
50: (7b) *(u64 *)(r0 +0) = r1
51: (05) goto pc+10
52: (b7) r1 = 1
; __u64 init = 1;
53: (7b) *(u64 *)(r10 -264) = r1
54: (bf) r2 = r10
55: (07) r2 += -256
56: (bf) r3 = r10
57: (07) r3 += -264
; bpf_map_update_elem(&exec_count, &key, &init, BPF_NOEXIST);
58: (18) r1 = map[id:6]
60: (b7) r4 = 1
61: (85) call htab_map_update_elem#202992
62: (bf) r3 = r10
; bpf_printk("execve: %s\n", key.path);
63: (07) r3 += -256
64: (18) r1 = map[id:8][0]+0
66: (b7) r2 = 12
67: (85) call bpf_trace_printk#-64800
; }
68: (b7) r0 = 0
69: (95) exit
また、JITコンパイルされたマシンコードも確認できる。
$ sudo bpftool prog dump jited id 17
出力は以下の通り
int handle_execve_tp(struct trace_event_raw_sys_enter * ctx):
bpf_prog_8236b54ceef5a3ce_handle_execve_tp:
; const char *filename = (const char *)ctx->args[0];
0: nopl 0x0(%rax,%rax,1)
5: xchg %ax,%ax
7: push %rbp
8: mov %rsp,%rbp
b: sub $0x108,%rsp
12: mov 0x10(%rdi),%rdx
16: xor %edi,%edi
; struct path_key key = {};
18: mov %rdi,-0x8(%rbp)
1c: mov %rdi,-0x10(%rbp)
20: mov %rdi,-0x18(%rbp)
24: mov %rdi,-0x20(%rbp)
28: mov %rdi,-0x28(%rbp)
2c: mov %rdi,-0x30(%rbp)
30: mov %rdi,-0x38(%rbp)
34: mov %rdi,-0x40(%rbp)
38: mov %rdi,-0x48(%rbp)
3c: mov %rdi,-0x50(%rbp)
40: mov %rdi,-0x58(%rbp)
44: mov %rdi,-0x60(%rbp)
48: mov %rdi,-0x68(%rbp)
4c: mov %rdi,-0x70(%rbp)
50: mov %rdi,-0x78(%rbp)
54: mov %rdi,-0x80(%rbp)
58: mov %rdi,-0x88(%rbp)
5f: mov %rdi,-0x90(%rbp)
66: mov %rdi,-0x98(%rbp)
6d: mov %rdi,-0xa0(%rbp)
74: mov %rdi,-0xa8(%rbp)
7b: mov %rdi,-0xb0(%rbp)
82: mov %rdi,-0xb8(%rbp)
89: mov %rdi,-0xc0(%rbp)
90: mov %rdi,-0xc8(%rbp)
97: mov %rdi,-0xd0(%rbp)
9e: mov %rdi,-0xd8(%rbp)
a5: mov %rdi,-0xe0(%rbp)
ac: mov %rdi,-0xe8(%rbp)
b3: mov %rdi,-0xf0(%rbp)
ba: mov %rdi,-0xf8(%rbp)
c1: mov %rdi,-0x100(%rbp)
c8: mov %rbp,%rdi
;
cb: add $0xffffffffffffff00,%rdi
; long n = bpf_probe_read_user_str(key.path, sizeof(key.path), filename);
d2: mov $0x100,%esi
d7: call 0xffffffff811c26c0
dc: mov $0x1,%edi
; if (n <= 0) {
e1: cmp %rax,%rdi
e4: jg 0xffffffffc0012a97
ea: mov %rbp,%rsi
; __u64 *val = bpf_map_lookup_elem(&exec_count, &key);
ed: add $0xffffffffffffff00,%rsi
f4: movabs $0xffff88810222cc00,%rdi
fe: call 0xffffffff81202fc0
103: test %rax,%rax
106: je 0xffffffffc0012a32
108: add $0x130,%rax
; if (val) {
10e: test %rax,%rax
111: je 0xffffffffc0012a45
; *val += 1;
113: mov 0x0(%rax),%rdi
117: add $0x1,%rdi
11b: mov %rdi,0x0(%rax)
11f: jmp 0xffffffffc0012a79
121: mov $0x1,%edi
; __u64 init = 1;
126: mov %rdi,-0x108(%rbp)
12d: mov %rbp,%rsi
130: add $0xffffffffffffff00,%rsi
137: mov %rbp,%rdx
13a: add $0xfffffffffffffef8,%rdx
; bpf_map_update_elem(&exec_count, &key, &init, BPF_NOEXIST);
141: movabs $0xffff88810222cc00,%rdi
14b: mov $0x1,%ecx
150: call 0xffffffff81205b60
155: mov %rbp,%rdx
; bpf_printk("execve: %s\n", key.path);
158: add $0xffffffffffffff00,%rdx
15f: movabs $0xffffc9000066a000,%rdi
169: mov $0xc,%esi
16e: call 0xffffffff811c4550
; }
173: xor %eax,%eax
175: leave
176: jmp 0xffffffff82003480
元のC言語コード→eBPF命令→ネイティブCPU命令に変換されていることが分かる
eBPFマップのリストと管理
以下のコマンドでeBPFマップを一覧表示できる。
$ sudo bpftool map list
出力は以下の通り
2: array name iterator.rodata flags 0x480
key 4B value 98B max_entries 1 memlock 4096B
btf_id 2 frozen
6: hash name exec_count flags 0x0
key 256B value 8B max_entries 16384 memlock 4325376B
btf_id 26
idを知っていればダンプできる。
$ sudo bpftool map dump id 6
出力は以下の通り
また、以下の方法でeBPFマップの検索、キーの更新、削除もできる
検索
keyhex=$(python3 - <<'PY'
s=b"/bin/bash\0".ljust(256, b"\x00")
print(" ".join(f"{b:02x}" for b in s))
PY
)
sudo bpftool map lookup id 5 key hex $keyhex
{
"key": {
"path": "/bin/bash"
},
"value": 1
}
s=b"/bin/bash\0": 探したいファイル名
.ljust(256, b"\x00"): 256バイトに足りない部分を0で埋める
print(..): /bin/bashを16進数に変換
キーの更新
keyhex=$(python3 - <<'PY'
s=b"/bin/bash\0".ljust(256, b"\x00")
print(" ".join(f"{b:02x}" for b in s))
PY
)
sudo bpftool map update id 5 key hex $keyhex value hex 2a 00 00 00 00 00 00 00
{
"key": {
"path": "/bin/bash"
},
"value": 42
}
削除
keyhex=$(python3 - <<'PY'
s=b"/bin/bash\0".ljust(256, b"\x00")
print(" ".join(f"{b:02x}" for b in s))
PY
)
sudo bpftool map delete id 5 key hex $keyhex
$ sudo bpftool map lookup id 6 key hex $keyhex
key:
2f 62 69 6e 2f 62 61 73 68 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Not found
デバッグとトレース
今までは
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
でログをprintしていたが、以下でも実現できる
$ sudo bpftool prog trace
vmlinux.hファイルの生成
ざっくりいうと
vmlinux.hは現在のカーネル内のデータ構造を1つのファイルにまとめたもの。めっちゃでかいヘッダーファイル。
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
利用可能なeBPF機能をリストアップする
$ sudo bpftool feature probe kernel
Scanning eBPF helper functions...
eBPF helpers supported for program type socket_filter:
- bpf_map_lookup_elem
- bpf_map_update_elem
- bpf_map_delete_elem
- bpf_ktime_get_ns
- bpf_get_prandom_u32
- bpf_get_smp_processor_id
- bpf_tail_call
- bpf_perf_event_output
- bpf_skb_load_bytes
- bpf_get_current_task
...
bpftop
eBPFプログラムをリアルタイムでモニタリングできる。
$ sudo bpftop
↑統計情報
左上(プログラム情報): プログラムID、タイプ、名前
右上(総CPU%): CPU使用率
左下(1秒当たりのイベント数)
右下(ナノ秒単位の平均ランタイム):

