はじめに
eBPF(Extended Berkeley Packet Filter)は、Linuxカーネルで動作するプログラムを効果的に実行できる仕組みです。この記事では、eBPFを使用してLinuxシステムコールをトレースする方法を紹介します。
システムコール一覧
Linuxのシステムコール一覧です。
システムコール | 説明 |
---|---|
accept | 新しい接続を受け入れる。通常、サーバーソケットで使用されます。 |
bind | ソケットにアドレスを関連付ける。 |
close | ファイルディスクリプタやソケットなどの資源を解放する。 |
connect | クライアントがサーバーに接続するために使用される。 |
creat | ファイルを作成する。新しいファイルが作成され、既存のファイルが更新される可能性があります。 |
dup | ファイルディスクリプタを複製する。 |
execve | 新しいプログラムを実行する。 |
fcntl | ファイルディスクリプタの操作を行う。 |
fork | 新しいプロセスを作成する。 |
fstat | ファイルのステータスを取得する。 |
listen | ソケットが接続待ち状態になるように設定する。 |
lstat | ファイルのステータスを取得するが、シンボリックリンクの場合はリンク先ではなくリンク自体のステータスを取得する。 |
open | ファイルを開く。存在しない場合は新しいファイルを作成する。 |
openat | 相対パスでファイルを開く。openと似ていますが、ディレクトリファイルディスクリプタを指定することができます。 |
poll | ファイルディスクリプタの状態をポーリングする。 |
read | ファイルからデータを読み取る。 |
recv | ソケットからデータを受け取る。 |
recvfrom | ソケットからデータを受け取り、送信元の情報も取得する。 |
send | ソケットにデータを送信する。 |
sendto | ソケットにデータを送信し、送信先の情報を指定する。 |
socket | 新しいソケットを作成する。 |
stat | ファイルのステータスを取得する。lstatと異なり、シンボリックリンクの場合はリンク先のステータスを取得する。 |
write | ファイルにデータを書き込む。 |
eBPFプログラムの定義
まず、eBPFプログラムを定義します。以下は、特定のシステムコールが呼ばれた際にメッセージを出力する簡単なプログラムの例です。
int syscall_accept(void *ctx){
bpf_trace_printk("syscall_accept!");
return 0;
}
// 他のシステムコールについても同様に定義
BPFオブジェクトの定義
次に、BPFオブジェクトを定義し、システムコールに紐づけます。
from bcc import BPF
program = """
// 上記で定義したeBPFプログラム
"""
b = BPF(text=program)
# 各システムコールに対してkprobeをアタッチ
# 例:acceptシステムコール
syscall_accept = b.get_syscall_fnname("accept")
b.attach_kprobe(event=syscall_accept, fn_name="syscall_accept")
トレースの開始
最後に、トレースを開始します。
b.trace_print(fmt="TASK={0} PID={1} MESSAGE={5}")
結果例
TASK=b'example' PID=123 MESSAGE=b'syscall_accept!'
このようにして、特定のシステムコールが呼ばれるたびにメッセージが出力されます。
ソースコード全文
以下は、eBPFプログラムの完全なソースコードです。Linuxを起動しているだけで、read、writeのトレースが大量に出力されますので、read、writeをkprobeにアタッチする処理はコメントアウトしています。
SysCallTrc.py
#!/usr/bin/python
from bcc import BPF
# eBPFプログラムの定義
program = r"""
int syscall_accept(void *ctx){
bpf_trace_printk("syscall_accept!");
return 0;
}
int syscall_bind(void *ctx){
bpf_trace_printk("syscall_bind!");
return 0;
}
int syscall_close(void *ctx){
bpf_trace_printk("syscall_close!");
return 0;
}
int syscall_connect(void *ctx){
bpf_trace_printk("syscall_connect!");
return 0;
}
int syscall_creat(void *ctx){
bpf_trace_printk("syscall_creat!");
return 0;
}
int syscall_dup(void *ctx){
bpf_trace_printk("syscall_dup!");
return 0;
}
int syscall_execve(void *ctx){
bpf_trace_printk("syscall_execve!");
return 0;
}
int syscall_fcntl(void *ctx){
bpf_trace_printk("syscall_fcntl!");
return 0;
}
int syscall_fork(void *ctx){
bpf_trace_printk("syscall_fork!");
return 0;
}
int syscall_fstat(void *ctx){
bpf_trace_printk("syscall_fstat!");
return 0;
}
int syscall_listen(void *ctx){
bpf_trace_printk("syscall_listen!");
return 0;
}
int syscall_lstat(void *ctx){
bpf_trace_printk("syscall_lstat!");
return 0;
}
int syscall_open(void *ctx){
bpf_trace_printk("syscall_open!");
return 0;
}
int syscall_openat(void *ctx){
bpf_trace_printk("syscall_openat!");
return 0;
}
int syscall_poll(void *ctx){
bpf_trace_printk("syscall_poll!");
return 0;
}
int syscall_read(void *ctx){
bpf_trace_printk("syscall_read!");
return 0;
}
int syscall_recv(void *ctx){
bpf_trace_printk("syscall_recv!");
return 0;
}
int syscall_recvfrom(void *ctx){
bpf_trace_printk("syscall_recvfrom!");
return 0;
}
int syscall_send(void *ctx){
bpf_trace_printk("syscall_send!");
return 0;
}
int syscall_sendto(void *ctx){
bpf_trace_printk("syscall_sendto!");
return 0;
}
int syscall_socket(void *ctx){
bpf_trace_printk("syscall_socket!");
return 0;
}
int syscall_stat(void *ctx){
bpf_trace_printk("syscall_stat!");
return 0;
}
int syscall_write(void *ctx){
bpf_trace_printk("syscall_write!");
return 0;
}
"""
# BPFオブジェクトの定義
b = BPF(text=program)
# システムコールのkprobeをアタッチ
syscall_accept = b.get_syscall_fnname("accept")
syscall_bind = b.get_syscall_fnname("bind")
syscall_close = b.get_syscall_fnname("close")
syscall_connect = b.get_syscall_fnname("connect")
syscall_creat = b.get_syscall_fnname("creat")
syscall_dup = b.get_syscall_fnname("dup")
syscall_execve = b.get_syscall_fnname("execve")
syscall_fcntl = b.get_syscall_fnname("fcntl")
syscall_fork = b.get_syscall_fnname("fork")
syscall_fstat = b.get_syscall_fnname("fstat")
syscall_listen = b.get_syscall_fnname("listen")
syscall_lstat = b.get_syscall_fnname("lstat")
syscall_open = b.get_syscall_fnname("open")
syscall_openat = b.get_syscall_fnname("openat")
syscall_poll = b.get_syscall_fnname("poll")
syscall_read = b.get_syscall_fnname("read")
syscall_recv = b.get_syscall_fnname("recv")
syscall_recvfrom = b.get_syscall_fnname("recvfrom")
syscall_send = b.get_syscall_fnname("send")
syscall_sendto = b.get_syscall_fnname("sendto")
syscall_socket = b.get_syscall_fnname("socket")
syscall_stat = b.get_syscall_fnname("stat")
syscall_write = b.get_syscall_fnname("write")
b.attach_kprobe(event=syscall_accept, fn_name="syscall_accept")
b.attach_kprobe(event=syscall_bind, fn_name="syscall_bind")
b.attach_kprobe(event=syscall_close, fn_name="syscall_close")
b.attach_kprobe(event=syscall_connect, fn_name="syscall_connect")
b.attach_kprobe(event=syscall_creat, fn_name="syscall_creat")
b.attach_kprobe(event=syscall_dup, fn_name="syscall_dup")
b.attach_kprobe(event=syscall_execve, fn_name="syscall_execve")
b.attach_kprobe(event=syscall_fcntl, fn_name="syscall_fcntl")
b.attach_kprobe(event=syscall_fork, fn_name="syscall_fork")
b.attach_kprobe(event=syscall_fstat, fn_name="syscall_fstat")
b.attach_kprobe(event=syscall_listen, fn_name="syscall_listen")
b.attach_kprobe(event=syscall_lstat, fn_name="syscall_lstat")
b.attach_kprobe(event=syscall_open, fn_name="syscall_open")
b.attach_kprobe(event=syscall_openat, fn_name="syscall_openat")
b.attach_kprobe(event=syscall_poll, fn_name="syscall_poll")
#b.attach_kprobe(event=syscall_read, fn_name="syscall_read")
b.attach_kprobe(event=syscall_recv, fn_name="syscall_recv")
b.attach_kprobe(event=syscall_recvfrom, fn_name="syscall_recvfrom")
b.attach_kprobe(event=syscall_send, fn_name="syscall_send")
b.attach_kprobe(event=syscall_sendto, fn_name="syscall_sendto")
b.attach_kprobe(event=syscall_socket, fn_name="syscall_socket")
b.attach_kprobe(event=syscall_stat, fn_name="syscall_stat")
#b.attach_kprobe(event=syscall_write, fn_name="syscall_write")
# トレースの開始
b.trace_print(fmt="TASK={0} PID={1} MESSAGE={5}")
まとめ
eBPFを使用することで、システムコールのトレースが容易になります。他にも様々な情報を取得することが可能なので、必要に応じて拡張して利用することができます。
このサンプルプログラムの完全なソースコードは、GitHubリポジトリ で公開しています。ぜひご覧ください。