libbpf-bootstrap 開発ガイド: 通信にringbufを使用する - ブートストラップ

目次

コード

        通信データ構造部分 (BPF&ユーザーデータ共有用)

BPFプログラムセクション

機能の説明

ヘッダーファイルの導入手順

bpf_probe_read_str はファイル名を読み取ります

bpf_ringbuf_submit は、BPF リング バッファに情報を送信します。

bpf_map_delete_elem

ユーザープログラム部

リングバッファ__new

Ring_buffer__poll

リングバッファ__フリー

実行効果


コード

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 を使用して次のフィールドを読み取ります。

  1. BPF_CORE_READ(task, real_parent, tgid): 現在のプロセスの親プロセスのプロセス ID (PID) を読み取ります。 real_parent は、task_struct 構造体のフィールドであり、プロセスの親プロセスを示します。tgid は、task_struct 構造体のフィールドで、スレッド グループ ID を示します。シングルスレッド プロセスの場合は、PID です。
  2. 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);

  1. bpf_map__fd(skel->maps.rb): この関数呼び出しは、BPF プログラムで定義されたリング バッファである BPF マップのファイル記述子を取得します。 skel->maps.rb は、このリング バッファ マップへの参照です。skel は、ロードされ検証される BPF プログラム構造のインスタンスです。
  2. handle_event: これは関数ポインタであり、リングバッファからデータが読み込まれるたびに、指定された関数が呼び出されます。この例では、handle_even 関数は、BPF プログラムから送信されたすべてのイベントを処理します。
  3. NULL: これら 2 つの NULL パラメーターは、それぞれリング バッファーのコンテキスト オブジェクトとデストラクターを表します。この例では、どれも使用されていないため、NULL に設定されています。
  4. 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 */);

  1. rb: これは、ポーリングするリング バッファへのポインタです。
  2. 100: これはポーリング操作のタイムアウト (ミリ秒単位) です。これは、100 ミリ秒以内にリング バッファーに新しいデータがない場合、ring_buffer__poll 関数はタイムアウト エラーを返すことを意味します。
  3. ring_buffer__poll: この関数は、次の条件のいずれかが満たされるまで、現在のスレッドをブロックします: リング バッファーに新しいデータがある、タイムアウトが発生する (この例では 100 ミリ秒)、または割り込み信号が受信される。
  4. 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

おすすめ

転載: blog.csdn.net/qq_32378713/article/details/131744608