目录
代码
comm 数据结构部分(用于bpf&user 数据share)
BPF 程序部分
功能说明
头文件引入说明
bpf_probe_read_str 读取filename
bpf_ringbuf_submit 将信息提交到BPF 的 ring buffer 中
bpf_map_delete_elem
用户程序部分
ring_buffer__new
ring_buffer__poll
ring_buffer__free
执行效果
/* 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 */
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include "vmlinux.h"
#include
#include
#include
#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 中的进程执行和退出事件。它利用 tracepoint 机制跟踪 sched/sched_process_exec 和 sched/sched_process_exit 事件,然后记录相关信息。
#include
在给出的代码中,BPF_CORE_READ 被用来读取以下字段:
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];
};
这里其实是使用了柔性数组的一个技巧
因此想要知道实际的文件名的地址需要使用(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 ring buffer 是一种在内核和用户空间之间传输数据的机制。BPF 程序在内核中运行,当它们需要将数据传输到用户空间时(例如,将数据传输给一个监控工具或者诊断工具),它们可以将数据写入 ring buffer,然后用户空间的程序可以从 ring buffer 中读取数据。
bpf_ringbuf_submit 函数就是用来将数据写入 ring buffer 的。在这个函数被调用之后,用户空间的程序就可以从 ring buffer 中读取 e 中的数据了。这种机制可以使得 BPF 程序能够将数据高效地传输到用户空间,而不需要使用比较复杂和耗时的系统调用。
void bpf_ringbuf_submit(void *data, u64 flags);
参数解释如下:
从 exec_start map 中删除记录,因为进程已经退出,不再需要这个记录。
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include
#include
#include
#include
#include
#include
#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 = "";
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 ] [-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;
}
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
如果 ring buffer 创建成功,ring_buffer__new 函数将返回一个指向新 ring buffer 的指针。如果创建失败,它将返回 NULL。在这个例子中,返回的指针被存储在 rb 变量中。
目的是检查 ring buffer 中是否有新数据,这些数据可能来自 BPF 程序。如果有新数据,ring_buffer__poll 函数将触发在创建 ring buffer 时指定的回调函数 (handle_event),并将新数据作为参数传递给该函数。
下面是对这行代码的详细解释:
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
ring_buffer__free 是一个用于释放之前通过 ring_buffer__new 创建的 ring buffer 的函数。
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