BPFプログラム間のデータのやり取り
BPFマップを使えば,execve()
を実行したPIDのプロセスが終了するまでの時間を取得してみましょう。
今回は少し変更点が多めです。例のごとくリポジトリにv1.
タグとして保存していますので,
今回の変更点のポイントは次のとおりです。
- mapセクションにデータ保存用の連想配列を作る
execve()
が実行されたらその連想配列にPIDをキー,実行開始時刻をバリューとして保存する sched_
トレースポイントから,process_ exit プロセスの終了時の処理を追加する - 上記処理にて連想配列からPIDにマッチする実行開始時刻を取得する
- ユーザーランドにPIDと実行開始時刻その他の情報を通知する
まずexecsnoop.
にデータ送受信用のメンバーであるduration
を追加します。今回はexecve()
が呼ばれたときと,execve()
が呼ばれたときは,duration
を0としておくことでユーザーランドプログラムはどちらのイベントかを判断できるわけです。ちなみにduration
の単位はナノ秒です。
struct event {
pid_t pid;
pid_t ppid;
char comm[TASK_COMM_LEN];
char fname[32];
__u64 duration;
};
次にBPFプログラムであるexecsnoop.
の変更箇所です。
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, pid_t);
__type(value, u64);
} exec_start SEC(".maps");
int syscalls__execve(struct trace_event_raw_sys_enter *ctx)
{
struct event *event = NULL;
struct task_struct *task;
pid_t pid;
u64 ts;
event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event)
return 0;
/* PIDの取得 */
pid = event->pid = bpf_get_current_pid_tgid() >> 32;
(中略)
/* durationの初期化およびマップへのPIDと開始時刻の保存 */
event->duration = 0;
ts = bpf_ktime_get_ns();
bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);
まず最初に時刻情報を保存するためのexec_
」BPF_
」execve()
を実行したタスクのPIDfork()
後のPID),execve()
実行時の時刻情報です。時刻情報はbpf_
」
連想配列の保存はbpf_
」
BPF_
:キーが既に存在したらエラーとするNOEXIST (上書き禁止) BPF_
:キーが既に存在しないとエラーとするEXIST (新規作成禁止) BPF_
:キーの存在は問わないANY (新規作成と上書きどちらも許可)
bpf_
が失敗したときは負の値が返ってきます。今回はそのまま処理を継続する方針で実装しています。
これでexecve()
」execsnoop.
の中になります。
SEC("tracepoint/sched/sched_process_exit")
int sched_process_exit(struct trace_event_raw_sched_process_template* ctx)
{
pid_t pid;
u64 *start;
u64 end = 0, duration = 0;
struct event *event = NULL;
struct task_struct *task;
/* 呼び出し時刻とPIDの取得 */
end = bpf_ktime_get_ns();
pid = bpf_get_current_pid_tgid() >> 32;
/* PIDに一致するキーの,バリュー(開始時刻の情報)を取得。見つからなければ何もしない */
start = bpf_map_lookup_elem(&exec_start, &pid);
if (!start)
return 0;
duration = end - *start;
/* 連想配列からエントリーを削除 */
bpf_map_delete_elem(&exec_start, &pid);
/* ユーザーランドへの通知処理 */
event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event)
return 0;
task = (struct task_struct *)bpf_get_current_task();
event->ppid = (pid_t)BPF_CORE_READ(task, real_parent, tgid);
if (bpf_get_current_comm(&event->comm, sizeof(event->comm)) < 0)
goto err;
event->fname[0] = '\0';
event->duration = duration;
bpf_ringbuf_submit(event, 0);
return 0;
err:
bpf_ringbuf_discard(event, 0);
return 0;
}
後半はexecve()
と同じであり,bpf_
」
エントリーが不要になったらbpf_
」
あとはexecve()
と同じように通知するだけですね。今回はduration
に何らかの値が入っているはずです。
最後にユーザーランドプログラム側execsnoop.
)
/* データの出力側 */
fprintf(stdout, "%-5s % 8d % 8d % 12lld %-16s %-32s\n",
event->duration ? "END" : "START",
event->pid, event->ppid, event->duration,
event->comm, event->fname);
/* 実行時に最初に表示されるヘッダー */
fprintf(stdout, "%-5s %-8s %-8s %-12s %-16s %-32s\n",
"STATE", "PID", "PPID", "DURATION", "COMM", "FNAME");
実際に実行してみましょう。
$ git checkout r695/5 v1.5 $ make $ sudo ./execsnoop STATE PID PPID DURATION COMM FNAME START 662566 2276 0 tmux: server /bin/sh START 662567 662566 0 sh /usr/bin/byobu-status START 662568 2276 0 tmux: server /bin/sh START 662569 662567 0 byobu-status /usr/bin/sed START 662570 662568 0 sh /usr/bin/byobu-status END 0 662567 2197624 sed START 662572 662567 0 byobu-status /usr/bin/tmux START 662573 662570 0 byobu-status /usr/bin/sed END 0 662570 2137350 sed START 662575 662570 0 byobu-status /usr/bin/tmux END 0 662567 6393921 tmux: client END 0 662566 17840702 byobu-status
どうやら無事にプロセス終了時の経過時間が表示されているようです。
このようにeBPFを使えば,