1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
#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 = "<bpf@vger.kernel.org>";
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" },
{},
};
// argp的回调函数,每当解析到一个参数,就调用一次
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case 'v':
// 如果是'v',开启verbose
env.verbose = true;
break;
case 'd':
// 如果是'd',转成long型并赋值到env中
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;
}
// 定义用于解析命令行参数的argp配置
static const struct argp argp = {
// 指定可接受的选项
.options = opts,
// 指定解析回调函数
.parser = parse_arg,
// 指定帮助文档
.doc = argp_program_doc,
};
// libbpf错误日志的回调函数
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
// 根据命令行输入(-v选项)决定是否打印LIBBPF_DEBUG级别的日志
if (level == LIBBPF_DEBUG && !env.verbose)
return 0;
return vfprintf(stderr, format, args);
}
static volatile bool exiting = false;
// 捕获Ctrl-C退出信号时的回调函数
static void sig_handler(int sig)
{
// 将exiting置为true,提示退出程序
exiting = true;
}
// 接收到事件信息时的回调函数
// data即指向Ring Buffer中传过来的事件数据
// data_sz表示事件数据的字节大小,可方便校验或处理变长数据,本例中事件结构体大小是固定的,因此未使用
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;
// 获取当前时间,即UNIX时间戳
time(&t);
// 转换为本地时间结构tm,方便后续格式化输出
tm = localtime(&t);
// 格式化时间字符串到ts,例如15:34:12
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
if (e->exit_event) {
// 如果exit_event为true,则表示是一个进程的退出事件
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 {
// 如果exit_event为false,则表示是一个进程的启动事件
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;
// 通过argp库解析命令行参数
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
if (err)
return err;
// 设置libbpf的日志打印回调函数为libbpf_print_fn
libbpf_set_print(libbpf_print_fn);
// 设置信号处理函数sig_handler,用于捕获Ctrl-C等退出信号
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
// 载入eBPF程序bootstrap
skel = bootstrap_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
// 修改eBPF程序中的min_duration_ns变量,该变量为const类型,因此位于ELF文件的只读数据段rodata
skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL;
// 加载并验证eBPF程序
err = bootstrap_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
// 将eBPF程序的指定函数附加到指定的tracepoint
err = bootstrap_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
// 创建一个Ring Buffer的消费者,用于接收eBPF程序发送到用户态的事件数据
// 参数一:用户态程序读Ring Buffer需要有该映射的文件描述符(fd),可通过bpf_map__fd获取
// 参数二:指定回调函数handle_event处理接收到的事件数据
// 参数三:用户上下文ctx,可传递自定义参数给回调函数handle_event
// 参数四:高级用法,通常填NULL
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;
}
printf("%-8s %-5s %-16s %-7s %-7s %s\n", "TIME", "EVENT", "COMM", "PID", "PPID",
"FILENAME/EXIT CODE");
// 捕获Ctrl-C退出信号后,回调函数sig_handler()将exiting置为true
while (!exiting) {
// 持续调用ring_buffer__poll()函数监听内核发送过来的事件
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
// 如果出现错误,同样退出
if (err == -EINTR) {
err = 0;
break;
}
if (err < 0) {
printf("Error polling perf buffer: %d\n", err);
break;
}
}
cleanup:
// 程序退出时释放Ring Buffer和eBPF相关资源
ring_buffer__free(rb);
bootstrap_bpf__destroy(skel);
return err < 0 ? -err : 0;
}
|