目次
bpf_probe_read_str はファイル名を読み取ります
bpf_ringbuf_submit は、BPF リング バッファに情報を送信します。
コード
comm データ構造部分 (bpf およびユーザー データ共有用)
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2020 Facebook */
#ifndef __BOOTSTRAP_H
#define __BOOTSTRAP_H
#define TASK_COMM_LEN 16
#define MAX_FILENAME_LEN 127
struct event {
int pid;
int ppid;
unsigned exit_code;
unsigned long long duration_ns;
char comm[TASK_COMM_LEN];
char filename[MAX_FILENAME_LEN];
bool exit_event;
};
#endif /* __BOOTSTRAP_H */
BPFプログラムセクション
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "bootstrap.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 8192);
__type(key, pid_t);
__type(value, u64);
} exec_start SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} rb SEC(".maps");
const volatile unsigned long long min_duration_ns = 0;
SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
struct task_struct *task;
unsigned fname_off;
struct event *e;
pid_t pid;
u64 ts;
/* remember time exec() was executed for this PID */
pid = bpf_get_current_pid_tgid() >> 32;
ts = bpf_ktime_get_ns();
bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);
/* don't emit exec events when minimum duration is specified */
if (min_duration_ns)
return 0;
/* reserve sample from BPF ringbuf */
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (!e)
return 0;
/* fill out the sample with data */
task = (struct task_struct *)bpf_get_current_task();
e->exit_event = false;
e->pid = pid;
e->ppid = BPF_CORE_READ(task, real_parent, tgid);
bpf_get_current_comm(&e->comm, sizeof(e->comm));
fname_off = ctx->__data_loc_filename & 0xFFFF;
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);
/* successfully submit it to user-space for post-processing */
bpf_ringbuf_submit(e, 0);
return 0;
}
SEC("tp/sched/sched_process_exit")
int handle_exit(struct trace_event_raw_sched_process_template *ctx)
{
struct task_struct *task;
struct event *e;
pid_t pid, tid;
u64 id, ts, *start_ts, duration_ns = 0;
/* get PID and TID of exiting thread/process */
id = bpf_get_current_pid_tgid();
pid = id >> 32;
tid = (u32)id;
/* ignore thread exits */
if (pid != tid)
return 0;
/* if we recorded start of the process, calculate lifetime duration */
start_ts = bpf_map_lookup_elem(&exec_start, &pid);
if (start_ts)
duration_ns = bpf_ktime_get_ns() - *start_ts;
else if (min_duration_ns)
return 0;
bpf_map_delete_elem(&exec_start, &pid);
/* if process didn't live long enough, return early */
if (min_duration_ns && duration_ns < min_duration_ns)
return 0;
/* reserve sample from BPF ringbuf */
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (!e)
return 0;
/* fill out the sample with data */
task = (struct task_struct *)bpf_get_current_task();
e->exit_event = true;
e->duration_ns = duration_ns;
e->pid = pid;
e->ppid = BPF_CORE_READ(task, real_parent, tgid);
e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
/* send data to user-space for post-processing */
bpf_ringbuf_submit(e, 0);
return 0;
}
機能の説明
この BPF プログラムの主な目的は、Linux でのプロセスの実行と終了イベントを追跡することです。トレースポイント メカニズムを使用して sched/sched_process_exec および sched/sched_process_exit イベントを追跡し、関連情報を記録します。
ヘッダーファイルの導入手順
#include <bpf/bpf_core_read.h> このヘッダー ファイルは、カーネル データ構造のフィールド値を読み取るために使用できる BPF プログラム用の BPF_CORE_READ マクロを提供します。これは BPF CO-RE (Compile Once, Run Everywhere) テクノロジの一部であり、カーネルのデータ構造が異なる場合でも、BPF プログラムを異なるバージョンのカーネルで実行できるようにします。
指定されたコードでは、BPF_CORE_READ を使用して次のフィールドを読み取ります。
- BPF_CORE_READ(task, real_parent, tgid): 現在のプロセスの親プロセスのプロセス ID (PID) を読み取ります。 real_parent は、task_struct 構造体のフィールドであり、プロセスの親プロセスを示します。tgid は、task_struct 構造体のフィールドで、スレッド グループ ID を示します。シングルスレッド プロセスの場合は、PID です。
- BPF_CORE_READ(task, exit_code): プロセスの終了ステータス コードを読み取ります。 exit_code は、プロセスの終了ステータス コードを表す task_struct 構造体のフィールドです。
bpf_probe_read_str はファイル名を読み取ります
fname_off = ctx->__data_loc_filename & 0xFFFF;
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);
ここでは、最初に構造を確認する必要があります:trace_event_raw_sched_process_exec
struct trace_event_raw_sched_process_exec {
struct trace_entry ent;
u32 __data_loc_filename;
pid_t pid;
pid_t old_pid;
char __data[0];
};
これは実際にはフレキシブルアレイを使用したテクニックです
- structtrace_entry ent;:trace_entry は、Linux トレースポイント メカニズムでトレース イベントを表すために使用される一般的な構造体です。これには、イベントのタイムスタンプ、プロセス ID (PID)、スレッド ID (TID) などの基本情報が含まれています。
- u32 __data_loc_filename;: このフィールドは、コンテキスト内のファイル名のオフセット (下位 16 ビット) と長さ (上位 16 ビット) を含む 32 ビット整数です。ファイル名は、プロセスによって実行されるファイルの名前です。
- pid_t pid;: このフィールドは、新しく開始されたプロセスのプロセス ID (PID) を表します。
- pid_t old_pid;: このフィールドは、最初に実行されていたプロセスのプロセス ID を表します。新しいプロセスが実行を開始すると、元の実行プロセスが置き換えられます。
- char __data[0];: これは、構造に続くデータを表すために使用される長さ 0 の配列です。この手法は、可変長データを表すために C で一般的に使用されます。この構造では、ファイル名の実際のデータを格納するために __data が使用されます。ファイル名の場所は、__data_loc_filename フィールドで確認できます。
したがって、実際のファイル名のアドレスを知りたい場合は、 (void *)ctx + fname_off を使用する必要があります。
bpf_probe_read_str 関数は、BPF (Berkeley Packet Filter) ライブラリの関数であり、ユーザー空間またはカーネル空間から null で終了する文字列を読み取るために使用されます。
int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr);
bpf_ringbuf_submit は、BPF リング バッファに情報を送信します。
BPF リング バッファは、カーネルとユーザー空間の間でデータを転送するためのメカニズムです。 BPF プログラムはカーネル内で実行され、データをユーザー空間に転送する必要がある場合 (たとえば、監視ツールや診断ツールにデータを転送するため)、BPF プログラムはデータをリング バッファーに書き込むことができ、その後、ユーザー空間プログラムは次のことを行うことができます。リングバッファからデータを読み込み、データを読み込みます。
bpf_ringbuf_submit 関数は、リング バッファにデータを書き込むために使用されます。この関数が呼び出された後、ユーザー空間プログラムはリング バッファから e のデータを読み取ることができます。このメカニズムにより、BPF プログラムは、複雑で時間のかかるシステム コールを使用せずに、データをユーザー空間に効率的に転送できます。
void bpf_ringbuf_submit(void *data, u64 flags);
パラメータは次のように説明されます。
- void *data: これは、リング バッファに送信するデータへのポインタです。これは通常、ユーザー空間に転送するデータを含む BPF プログラム内の構造体へのポインターです。
- u64 フラグ: これは、関数の動作を制御するために使用されるフラグ ビットです。現時点では、フラグがまだ定義されていないため、このパラメータは常に 0 に設定する必要があります。
bpf_map_delete_elem
プロセスが終了して不要になったため、exec_start マップからレコードを削除します。
ユーザープログラム部
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include <argp.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "bootstrap.h"
#include "bootstrap.skel.h"
static struct env {
bool verbose;
long min_duration_ms;
} env;
const char *argp_program_version = "bootstrap 0.0";
const char *argp_program_bug_address = "<[email protected]>";
const char argp_program_doc[] = "BPF bootstrap demo application.\n"
"\n"
"It traces process start and exits and shows associated \n"
"information (filename, process duration, PID and PPID, etc).\n"
"\n"
"USAGE: ./bootstrap [-d <min-duration-ms>] [-v]\n";
static const struct argp_option opts[] = {
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
{ "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" },
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case 'v':
env.verbose = true;
break;
case 'd':
errno = 0;
env.min_duration_ms = strtol(arg, NULL, 10);
if (errno || env.min_duration_ms <= 0) {
fprintf(stderr, "Invalid duration: %s\n", arg);
argp_usage(state);
}
break;
case ARGP_KEY_ARG:
argp_usage(state);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static const struct argp argp = {
.options = opts,
.parser = parse_arg,
.doc = argp_program_doc,
};
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
if (level == LIBBPF_DEBUG && !env.verbose)
return 0;
return vfprintf(stderr, format, args);
}
static volatile bool exiting = false;
static void sig_handler(int sig)
{
exiting = true;
}
static int handle_event(void *ctx, void *data, size_t data_sz)
{
const struct event *e = data;
struct tm *tm;
char ts[32];
time_t t;
time(&t);
tm = localtime(&t);
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
if (e->exit_event) {
printf("%-8s %-5s %-16s %-7d %-7d [%u]", ts, "EXIT", e->comm, e->pid, e->ppid,
e->exit_code);
if (e->duration_ns)
printf(" (%llums)", e->duration_ns / 1000000);
printf("\n");
} else {
printf("%-8s %-5s %-16s %-7d %-7d %s\n", ts, "EXEC", e->comm, e->pid, e->ppid,
e->filename);
}
return 0;
}
int main(int argc, char **argv)
{
struct ring_buffer *rb = NULL;
struct bootstrap_bpf *skel;
int err;
/* Parse command line arguments */
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err)
return err;
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Cleaner handling of Ctrl-C */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* Load and verify BPF application */
skel = bootstrap_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
/* Parameterize BPF code with minimum duration parameter */
skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL;
/* Load & verify BPF programs */
err = bootstrap_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
/* Attach tracepoints */
err = bootstrap_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
/* Set up ring buffer polling */
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
if (!rb) {
err = -1;
fprintf(stderr, "Failed to create ring buffer\n");
goto cleanup;
}
/* Process events */
printf("%-8s %-5s %-16s %-7s %-7s %s\n", "TIME", "EVENT", "COMM", "PID", "PPID",
"FILENAME/EXIT CODE");
while (!exiting) {
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
/* Ctrl-C will cause -EINTR */
if (err == -EINTR) {
err = 0;
break;
}
if (err < 0) {
printf("Error polling perf buffer: %d\n", err);
break;
}
}
cleanup:
/* Clean up */
ring_buffer__free(rb);
bootstrap_bpf__destroy(skel);
return err < 0 ? -err : 0;
}
リングバッファ__new
rb =ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
- bpf_map__fd(skel->maps.rb): この関数呼び出しは、BPF プログラムで定義されたリング バッファである BPF マップのファイル記述子を取得します。 skel->maps.rb は、このリング バッファ マップへの参照です。skel は、ロードされ検証される BPF プログラム構造のインスタンスです。
- handle_event: これは関数ポインタであり、リングバッファからデータが読み込まれるたびに、指定された関数が呼び出されます。この例では、handle_even 関数は、BPF プログラムから送信されたすべてのイベントを処理します。
- NULL: これら 2 つの NULL パラメーターは、それぞれリング バッファーのコンテキスト オブジェクトとデストラクターを表します。この例では、どれも使用されていないため、NULL に設定されています。
- ring_buffer__new: この関数は新しいリング バッファ インスタンスを作成します。この新しいインスタンスは、BPF プログラムから送信されたデータを読み取るために使用されます。
リング バッファが正常に作成されると、ring_buffer__new 関数は新しいリング バッファへのポインタを返します。作成に失敗した場合は NULL を返します。この例では、返されたポインターは rb 変数に格納されます。
Ring_buffer__poll
目的は、BPF プログラムからの可能性のある新しいデータがリング バッファーにあるかどうかを確認することです。新しいデータがある場合、ring_buffer__poll 関数はリング バッファの作成時に指定されたコールバック関数 (handle_event) をトリガーし、新しいデータをパラメータとして関数に渡します。
このコード行の詳細な説明は次のとおりです。
err =ring_buffer__poll(rb, 100 /* タイムアウト, ms */);
- rb: これは、ポーリングするリング バッファへのポインタです。
- 100: これはポーリング操作のタイムアウト (ミリ秒単位) です。これは、100 ミリ秒以内にリング バッファーに新しいデータがない場合、ring_buffer__poll 関数はタイムアウト エラーを返すことを意味します。
- ring_buffer__poll: この関数は、次の条件のいずれかが満たされるまで、現在のスレッドをブロックします: リング バッファーに新しいデータがある、タイムアウトが発生する (この例では 100 ミリ秒)、または割り込み信号が受信される。
- err: この変数には、ring_buffer__poll 関数の戻り値が格納されます。関数がリング バッファからデータを正常に読み取ると、読み取られたイベントの数が返されます。タイムアウトが発生した場合は 0 を返します。エラーが発生した場合 (割り込みなどにより)、負のエラー コードが返されます。
リングバッファ__フリー
ring_buffer__free は、ring_buffer__new によって以前に作成されたリング バッファを解放するために使用される関数です。
実行効果
TIME EVENT COMM PID PPID FILENAME/EXIT CODE
21:29:15 EXEC gio-launch-desk 34135 2947 /usr/lib/x86_64-linux-gnu/glib-2.0/gio-launch-desktop
21:29:15 EXEC google-chrome-s 34135 2947 /usr/bin/google-chrome-stable
21:29:15 EXEC readlink 34138 34135 /usr/bin/readlink
21:29:15 EXIT readlink 34138 34135 [0] (0ms)
21:29:15 EXEC dirname 34139 34135 /usr/bin/dirname
21:29:15 EXIT dirname 34139 34135 [0] (0ms)
21:29:15 EXEC mkdir 34140 34135 /usr/bin/mkdir
21:29:15 EXIT mkdir 34140 34135 [0] (0ms)
21:29:15 EXEC cat 34141 34135 /usr/bin/cat
21:29:15 EXEC chrome 34135 2947 /opt/google/chrome/chrome
21:29:15 EXEC cat 34142 34135 /usr/bin/cat
21:29:15 EXEC chrome_crashpad 34144 34143 /opt/google/chrome/chrome_crashpad_handler
21:29:15 EXIT chrome 34143 34135 [0]
21:29:15 EXEC chrome_crashpad 34146 34145 /opt/google/chrome/chrome_crashpad_handler
21:29:15 EXIT chrome_crashpad 34145 34144 [0]
21:29:15 EXIT chrome 34151 34135 [0]
21:29:15 EXEC chrome 34152 34135 /opt/google/chrome/chrome
21:29:15 EXEC chrome 34153 34135 /opt/google/chrome/chrome
21:29:15 EXEC nacl_helper 34154 34153 /opt/google/chrome/nacl_helper