LoginSignup
0
1

eBPFを使用してLinuxシステムコールをトレースする

Posted at

はじめに

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リポジトリ で公開しています。ぜひご覧ください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1