mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 10:14:44 +08:00
Deploying to gh-pages from @ eunomia-bpf/bpf-developer-tutorial@c120bb4912 🚀
This commit is contained in:
@@ -236,15 +236,15 @@ typedef unsigned int u32;
|
||||
typedef int pid_t;
|
||||
const pid_t pid_filter = 0;
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("tp/syscalls/sys_enter_write")
|
||||
SEC("tp/syscalls/sys_enter_write")
|
||||
int handle_tp(void *ctx)
|
||||
{
|
||||
pid_t pid = bpf_get_current_pid_tgid() >> 32;
|
||||
if (pid_filter && pid != pid_filter)
|
||||
return 0;
|
||||
bpf_printk("BPF triggered sys_enter_write from PID %d.\n", pid);
|
||||
bpf_printk("BPF triggered sys_enter_write from PID %d.\n", pid);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -270,7 +270,7 @@ Packing ebpf object and config into package.json...
|
||||
Runing eBPF program...
|
||||
</code></pre>
|
||||
<p>运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:</p>
|
||||
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
|
||||
<pre><code class="language-console">$ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep "BPF triggered sys_enter_write"
|
||||
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
|
||||
<...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345.
|
||||
</code></pre>
|
||||
@@ -283,7 +283,7 @@ Runing eBPF program...
|
||||
<p>如上所述, eBPF 程序的基本框架包括:</p>
|
||||
<ul>
|
||||
<li>包含头文件:需要包含 <linux/bpf.h> 和 <bpf/bpf_helpers.h> 等头文件。</li>
|
||||
<li>定义许可证:需要定义许可证,通常使用 "Dual BSD/GPL"。</li>
|
||||
<li>定义许可证:需要定义许可证,通常使用 "Dual BSD/GPL"。</li>
|
||||
<li>定义 BPF 函数:需要定义一个 BPF 函数,例如其名称为 handle_tp,其参数为 void *ctx,返回值为 int。通常用 C 语言编写。</li>
|
||||
<li>使用 BPF 助手函数:在例如 BPF 函数中,可以使用 BPF 助手函数 bpf_get_current_pid_tgid() 和 bpf_printk()。</li>
|
||||
<li>返回值</li>
|
||||
|
||||
@@ -203,9 +203,9 @@ hardirqs 和 softirqs 是 Linux 内核中两种不同类型的中断处理程序
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "hardirqs.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "hardirqs.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
|
||||
#define MAX_ENTRIES 256
|
||||
|
||||
@@ -219,21 +219,21 @@ struct {
|
||||
__type(key, u32);
|
||||
__type(value, u32);
|
||||
__uint(max_entries, 1);
|
||||
} cgroup_map SEC(".maps");
|
||||
} cgroup_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, u32);
|
||||
__type(value, u64);
|
||||
} start SEC(".maps");
|
||||
} start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, struct irq_key);
|
||||
__type(value, struct info);
|
||||
} infos SEC(".maps");
|
||||
} infos SEC(".maps");
|
||||
|
||||
static struct info zero;
|
||||
|
||||
@@ -302,31 +302,31 @@ static int handle_exit(int irq, struct irqaction *action)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tp_btf/irq_handler_entry")
|
||||
SEC("tp_btf/irq_handler_entry")
|
||||
int BPF_PROG(irq_handler_entry_btf, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_entry(irq, action);
|
||||
}
|
||||
|
||||
SEC("tp_btf/irq_handler_exit")
|
||||
SEC("tp_btf/irq_handler_exit")
|
||||
int BPF_PROG(irq_handler_exit_btf, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_exit(irq, action);
|
||||
}
|
||||
|
||||
SEC("raw_tp/irq_handler_entry")
|
||||
SEC("raw_tp/irq_handler_entry")
|
||||
int BPF_PROG(irq_handler_entry, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_entry(irq, action);
|
||||
}
|
||||
|
||||
SEC("raw_tp/irq_handler_exit")
|
||||
SEC("raw_tp/irq_handler_exit")
|
||||
int BPF_PROG(irq_handler_exit, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_exit(irq, action);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这段代码是一个 eBPF 程序,用于捕获和分析内核中硬件中断处理程序(hardirqs)的执行信息。程序的主要目的是获取中断处理程序的名称、执行次数和执行时间,并以直方图的形式展示执行时间的分布。让我们一步步分析这段代码。</p>
|
||||
<ol>
|
||||
@@ -336,9 +336,9 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "hardirqs.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "hardirqs.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
</code></pre>
|
||||
<p>该程序包含了 eBPF 开发所需的标准头文件,以及用于定义数据结构和映射的自定义头文件。</p>
|
||||
</li>
|
||||
@@ -363,25 +363,25 @@ const volatile bool do_count = false;
|
||||
<li>
|
||||
<p>定义 eBPF 程序的入口点:</p>
|
||||
<pre><code class="language-c">
|
||||
SEC("tp_btf/irq_handler_entry")
|
||||
SEC("tp_btf/irq_handler_entry")
|
||||
int BPF_PROG(irq_handler_entry_btf, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_entry(irq, action);
|
||||
}
|
||||
|
||||
SEC("tp_btf/irq_handler_exit")
|
||||
SEC("tp_btf/irq_handler_exit")
|
||||
int BPF_PROG(irq_handler_exit_btf, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_exit(irq, action);
|
||||
}
|
||||
|
||||
SEC("raw_tp/irq_handler_entry")
|
||||
SEC("raw_tp/irq_handler_entry")
|
||||
int BPF_PROG(irq_handler_entry, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_entry(irq, action);
|
||||
}
|
||||
|
||||
SEC("raw_tp/irq_handler_exit")
|
||||
SEC("raw_tp/irq_handler_exit")
|
||||
int BPF_PROG(irq_handler_exit, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_exit(irq, action);
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
</ul>
|
||||
<p>结合 libbpf 和 BTF,eBPF 程序可以在各种不同版本的内核上运行,而无需为每个内核版本单独编译。这极大地提高了 eBPF 生态系统的可移植性和兼容性,降低了开发和维护的难度。</p>
|
||||
<h2 id="什么是-bootstrap"><a class="header" href="#什么是-bootstrap">什么是 bootstrap</a></h2>
|
||||
<p>Bootstrap 是一个使用 libbpf 的完整应用,它利用 eBPF 程序来跟踪内核中的 exec() 系统调用(通过 SEC("tp/sched/sched_process_exec") handle_exec BPF 程序),这主要对应于新进程的创建(不包括 fork() 部分)。此外,它还跟踪进程的 exit() 系统调用(通过 SEC("tp/sched/sched_process_exit") handle_exit BPF 程序),以了解每个进程何时退出。</p>
|
||||
<p>Bootstrap 是一个使用 libbpf 的完整应用,它利用 eBPF 程序来跟踪内核中的 exec() 系统调用(通过 SEC("tp/sched/sched_process_exec") handle_exec BPF 程序),这主要对应于新进程的创建(不包括 fork() 部分)。此外,它还跟踪进程的 exit() 系统调用(通过 SEC("tp/sched/sched_process_exit") handle_exit BPF 程序),以了解每个进程何时退出。</p>
|
||||
<p>这两个 BPF 程序共同工作,允许捕获关于新进程的有趣信息,例如二进制文件的文件名,以及测量进程的生命周期,并在进程结束时收集有趣的统计信息,例如退出代码或消耗的资源量等。这是深入了解内核内部并观察事物如何真正运作的良好起点。</p>
|
||||
<p>Bootstrap 还使用 argp API(libc 的一部分)进行命令行参数解析,使得用户可以通过命令行选项配置应用行为。这种方式提供了灵活性,让用户能够根据实际需求自定义程序行为。虽然这些功能使用 eunomia-bpf 工具也可以实现,但是这里我们使用 libbpf 可以在用户态提供更高的可扩展性,不过也带来了不少额外的复杂度。</p>
|
||||
<h2 id="bootstrap"><a class="header" href="#bootstrap">Bootstrap</a></h2>
|
||||
@@ -200,29 +200,29 @@
|
||||
<h3 id="内核态-ebpf-程序-bootstrapbpfc"><a class="header" href="#内核态-ebpf-程序-bootstrapbpfc">内核态 eBPF 程序 bootstrap.bpf.c</a></h3>
|
||||
<pre><code class="language-c">// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
#include "vmlinux.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "bootstrap.h"
|
||||
#include "bootstrap.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
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");
|
||||
} exec_start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
} rb SEC(".maps");
|
||||
|
||||
const volatile unsigned long long min_duration_ns = 0;
|
||||
|
||||
SEC("tp/sched/sched_process_exec")
|
||||
SEC("tp/sched/sched_process_exec")
|
||||
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
|
||||
{
|
||||
struct task_struct *task;
|
||||
@@ -261,7 +261,7 @@ int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tp/sched/sched_process_exit")
|
||||
SEC("tp/sched/sched_process_exit")
|
||||
int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
{
|
||||
struct task_struct *task;
|
||||
@@ -312,30 +312,30 @@ int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
</code></pre>
|
||||
<p>这段代码是一个内核态 eBPF 程序(bootstrap.bpf.c),主要用于跟踪 exec() 和 exit() 系统调用。它通过 eBPF 程序捕获进程的创建和退出事件,并将相关信息发送到用户态程序进行处理。下面是对代码的详细解释。</p>
|
||||
<p>首先,我们引入所需的头文件,定义 eBPF 程序的许可证以及两个 eBPF maps:exec_start 和 rb。exec_start 是一个哈希类型的 eBPF map,用于存储进程开始执行时的时间戳。rb 是一个环形缓冲区类型的 eBPF map,用于存储捕获的事件数据,并将其发送到用户态程序。</p>
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "bootstrap.h"
|
||||
#include "bootstrap.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
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");
|
||||
} exec_start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
} rb SEC(".maps");
|
||||
|
||||
const volatile unsigned long long min_duration_ns = 0;
|
||||
</code></pre>
|
||||
<p>接下来,我们定义了一个名为 handle_exec 的 eBPF 程序,它会在进程执行 exec() 系统调用时触发。首先,我们从当前进程中获取 PID,记录进程开始执行的时间戳,然后将其存储在 exec_start map 中。</p>
|
||||
<pre><code class="language-c">SEC("tp/sched/sched_process_exec")
|
||||
<pre><code class="language-c">SEC("tp/sched/sched_process_exec")
|
||||
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
|
||||
{
|
||||
// ...
|
||||
@@ -368,7 +368,7 @@ int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
|
||||
return 0;
|
||||
</code></pre>
|
||||
<p>最后,我们定义了一个名为 handle_exit 的 eBPF 程序,它会在进程执行 exit() 系统调用时触发。首先,我们从当前进程中获取 PID 和 TID(线程 ID)。如果 PID 和 TID 不相等,说明这是一个线程退出,我们将忽略此事件。</p>
|
||||
<pre><code class="language-c">SEC("tp/sched/sched_process_exit")
|
||||
<pre><code class="language-c">SEC("tp/sched/sched_process_exit")
|
||||
int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
{
|
||||
// ...
|
||||
@@ -448,27 +448,27 @@ struct event {
|
||||
#include <time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include "bootstrap.h"
|
||||
#include "bootstrap.skel.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_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";
|
||||
"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" },
|
||||
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
|
||||
{ "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" },
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -482,7 +482,7 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
|
||||
errno = 0;
|
||||
env.min_duration_ms = strtol(arg, NULL, 10);
|
||||
if (errno || env.min_duration_ms <= 0) {
|
||||
fprintf(stderr, "Invalid duration: %s\n", arg);
|
||||
fprintf(stderr, "Invalid duration: %s\n", arg);
|
||||
argp_usage(state);
|
||||
}
|
||||
break;
|
||||
@@ -524,17 +524,17 @@ static int handle_event(void *ctx, void *data, size_t data_sz)
|
||||
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
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);
|
||||
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");
|
||||
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);
|
||||
printf("%-8s %-5s %-16s %-7d %-7d %s\n",
|
||||
ts, "EXEC", e->comm, e->pid, e->ppid, e->filename);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -561,7 +561,7 @@ int main(int argc, char **argv)
|
||||
/* Load and verify BPF application */
|
||||
skel = bootstrap_bpf__open();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -571,14 +571,14 @@ int main(int argc, char **argv)
|
||||
/* Load & verify BPF programs */
|
||||
err = bootstrap_bpf__load(skel);
|
||||
if (err) {
|
||||
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
|
||||
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");
|
||||
fprintf(stderr, "Failed to attach BPF skeleton\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -586,13 +586,13 @@ int main(int argc, char **argv)
|
||||
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");
|
||||
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");
|
||||
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 */
|
||||
@@ -601,7 +601,7 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
}
|
||||
if (err < 0) {
|
||||
printf("Error polling perf buffer: %d\n", err);
|
||||
printf("Error polling perf buffer: %d\n", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -623,8 +623,8 @@ cleanup:
|
||||
</code></pre>
|
||||
<p>接下来,我们使用 argp 库来解析命令行参数:</p>
|
||||
<pre><code class="language-c">static const struct argp_option opts[] = {
|
||||
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
|
||||
{ "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" },
|
||||
{ "verbose", 'v', NULL, 0, "Verbose debug output" },
|
||||
{ "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" },
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -649,7 +649,7 @@ libbpf_set_print(libbpf_print_fn);
|
||||
<p>接下来,我们打开 eBPF 脚手架(skeleton)文件,将最小持续时间参数传递给 eBPF 程序,并加载和附加 eBPF 程序:</p>
|
||||
<pre><code class="language-c">skel = bootstrap_bpf__open();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -657,13 +657,13 @@ skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL;
|
||||
|
||||
err = bootstrap_bpf__load(skel);
|
||||
if (err) {
|
||||
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
|
||||
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
err = bootstrap_bpf__attach(skel);
|
||||
if (err) {
|
||||
fprintf(stderr, "Failed to attach BPF skeleton\n");
|
||||
fprintf(stderr, "Failed to attach BPF skeleton\n");
|
||||
goto cleanup;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -671,7 +671,7 @@ if (err) {
|
||||
<pre><code class="language-c">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");
|
||||
fprintf(stderr, "Failed to create ring buffer\n");
|
||||
goto cleanup;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -217,21 +217,21 @@ Userspace:
|
||||
<p>内核态 eBPF 程序的实现逻辑主要是借助 perf event,对程序的堆栈进行定时采样,从而捕获程序的执行流程。</p>
|
||||
<pre><code class="language-c">// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2022 Meta Platforms, Inc. */
|
||||
#include "vmlinux.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
#include "profile.h"
|
||||
#include "profile.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("perf_event")
|
||||
SEC("perf_event")
|
||||
int profile(void *ctx)
|
||||
{
|
||||
int pid = bpf_get_current_pid_tgid() >> 32;
|
||||
@@ -266,13 +266,13 @@ int profile(void *ctx)
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
</code></pre>
|
||||
<p>这里定义了一个类型为 <code>BPF_MAP_TYPE_RINGBUF</code> 的 eBPF maps 。Ring Buffer 是一种高性能的循环缓冲区,用于在内核和用户空间之间传输数据。<code>max_entries</code> 设置了 Ring Buffer 的最大大小。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>定义 <code>perf_event</code> eBPF 程序:</p>
|
||||
<pre><code class="language-c">SEC("perf_event")
|
||||
<pre><code class="language-c">SEC("perf_event")
|
||||
int profile(void *ctx)
|
||||
</code></pre>
|
||||
<p>这里定义了一个名为 <code>profile</code> 的 eBPF 程序,它将在 perf 事件触发时执行。</p>
|
||||
@@ -342,7 +342,7 @@ int main(){
|
||||
/* Set up performance monitoring on a CPU/Core */
|
||||
pefd = perf_event_open(&attr, pid, cpu, -1, PERF_FLAG_FD_CLOEXEC);
|
||||
if (pefd < 0) {
|
||||
fprintf(stderr, "Fail to set up performance monitor on a CPU/Core\n");
|
||||
fprintf(stderr, "Fail to set up performance monitor on a CPU/Core\n");
|
||||
err = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -387,34 +387,34 @@ int main(){
|
||||
|
||||
for (i = 0; i < stack_sz; i++) {
|
||||
if (!result || result->size <= i || !result->entries[i].size) {
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result->entries[i].size == 1) {
|
||||
sym = &result->entries[i].syms[0];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %d [<%016llx>] %s+0x%llx %s:%ld\n",
|
||||
printf(" %d [<%016llx>] %s+0x%llx %s:%ld\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %d [<%016llx>] %s+0x%llx\n",
|
||||
printf(" %d [<%016llx>] %s+0x%llx\n",
|
||||
i, stack[i], sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
printf(" %d [<%016llx>]\n", i, stack[i]);
|
||||
for (j = 0; j < result->entries[i].size; j++) {
|
||||
sym = &result->entries[i].syms[j];
|
||||
if (sym->path && sym->path[0]) {
|
||||
printf(" %s+0x%llx %s:%ld\n",
|
||||
printf(" %s+0x%llx %s:%ld\n",
|
||||
sym->symbol, stack[i] - sym->start_address,
|
||||
sym->path, sym->line_no);
|
||||
} else {
|
||||
printf(" %s+0x%llx\n", sym->symbol,
|
||||
printf(" %s+0x%llx\n", sym->symbol,
|
||||
stack[i] - sym->start_address);
|
||||
}
|
||||
}
|
||||
@@ -431,23 +431,23 @@ static int event_handler(void *_ctx, void *data, size_t size)
|
||||
if (event->kstack_sz <= 0 && event->ustack_sz <= 0)
|
||||
return 1;
|
||||
|
||||
printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id);
|
||||
printf("COMM: %s (pid=%d) @ CPU %d\n", event->comm, event->pid, event->cpu_id);
|
||||
|
||||
if (event->kstack_sz > 0) {
|
||||
printf("Kernel:\n");
|
||||
printf("Kernel:\n");
|
||||
show_stack_trace(event->kstack, event->kstack_sz / sizeof(__u64), 0);
|
||||
} else {
|
||||
printf("No Kernel Stack\n");
|
||||
printf("No Kernel Stack\n");
|
||||
}
|
||||
|
||||
if (event->ustack_sz > 0) {
|
||||
printf("Userspace:\n");
|
||||
printf("Userspace:\n");
|
||||
show_stack_trace(event->ustack, event->ustack_sz / sizeof(__u64), event->pid);
|
||||
} else {
|
||||
printf("No Userspace Stack\n");
|
||||
printf("No Userspace Stack\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -205,19 +205,19 @@
|
||||
<p>理解了这两个队列的用途,我们就可以开始探究 tcpconnlat 的具体实现。tcpconnlat 的实现可以分为内核态和用户态两个部分,其中包括了几个主要的跟踪点:<code>tcp_v4_connect</code>, <code>tcp_v6_connect</code> 和 <code>tcp_rcv_state_process</code>。</p>
|
||||
<p>这些跟踪点主要位于内核中的 TCP/IP 网络栈。当执行相关的系统调用或内核函数时,这些跟踪点会被激活,从而触发 eBPF 程序的执行。这使我们能够捕获和测量 TCP 连接建立的整个过程。</p>
|
||||
<p>让我们先来看一下这些挂载点的源代码:</p>
|
||||
<pre><code class="language-c">SEC("kprobe/tcp_v4_connect")
|
||||
<pre><code class="language-c">SEC("kprobe/tcp_v4_connect")
|
||||
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
int BPF_KPROBE(tcp_v6_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_rcv_state_process")
|
||||
SEC("kprobe/tcp_rcv_state_process")
|
||||
int BPF_KPROBE(tcp_rcv_state_process, struct sock *sk)
|
||||
{
|
||||
return handle_tcp_rcv_state_process(ctx, sk);
|
||||
@@ -401,7 +401,7 @@ if (inet_opt && inet_opt->opt.srr) {
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "tcpconnlat.h"
|
||||
#include "tcpconnlat.h"
|
||||
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
@@ -420,13 +420,13 @@ struct {
|
||||
__uint(max_entries, 4096);
|
||||
__type(key, struct sock *);
|
||||
__type(value, struct piddata);
|
||||
} start SEC(".maps");
|
||||
} start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
|
||||
static int trace_connect(struct sock *sk)
|
||||
{
|
||||
@@ -489,43 +489,43 @@ cleanup:
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v4_connect")
|
||||
SEC("kprobe/tcp_v4_connect")
|
||||
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
int BPF_KPROBE(tcp_v6_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_rcv_state_process")
|
||||
SEC("kprobe/tcp_rcv_state_process")
|
||||
int BPF_KPROBE(tcp_rcv_state_process, struct sock *sk)
|
||||
{
|
||||
return handle_tcp_rcv_state_process(ctx, sk);
|
||||
}
|
||||
|
||||
SEC("fentry/tcp_v4_connect")
|
||||
SEC("fentry/tcp_v4_connect")
|
||||
int BPF_PROG(fentry_tcp_v4_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("fentry/tcp_v6_connect")
|
||||
SEC("fentry/tcp_v6_connect")
|
||||
int BPF_PROG(fentry_tcp_v6_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("fentry/tcp_rcv_state_process")
|
||||
SEC("fentry/tcp_rcv_state_process")
|
||||
int BPF_PROG(fentry_tcp_rcv_state_process, struct sock *sk)
|
||||
{
|
||||
return handle_tcp_rcv_state_process(ctx, sk);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这个eBPF(Extended Berkeley Packet Filter)程序主要用来监控并收集TCP连接的建立时间,即从发起TCP连接请求(<code>connect</code>系统调用)到连接建立完成(SYN-ACK握手过程完成)的时间间隔。这对于监测网络延迟、服务性能分析等方面非常有用。</p>
|
||||
<p>首先,定义了两个eBPF maps:<code>start</code>和<code>events</code>。<code>start</code>是一个哈希表,用于存储发起连接请求的进程信息和时间戳,而<code>events</code>是一个<code>PERF_EVENT_ARRAY</code>类型的map,用于将事件数据传输到用户态。</p>
|
||||
@@ -534,13 +534,13 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
__uint(max_entries, 4096);
|
||||
__type(key, struct sock *);
|
||||
__type(value, struct piddata);
|
||||
} start SEC(".maps");
|
||||
} start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
</code></pre>
|
||||
<p>在<code>tcp_v4_connect</code>和<code>tcp_v6_connect</code>的kprobe处理函数<code>trace_connect</code>中,会记录下发起连接请求的进程信息(进程名、进程ID和当前时间戳),并以socket结构作为key,存储到<code>start</code>这个map中。</p>
|
||||
<pre><code class="language-c">static int trace_connect(struct sock *sk)
|
||||
@@ -621,7 +621,7 @@ cleanup:
|
||||
while (!exiting) {
|
||||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
|
||||
fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
/* reset err to return 0 if exiting */
|
||||
@@ -643,7 +643,7 @@ cleanup:
|
||||
if (env.timestamp) {
|
||||
if (start_ts == 0)
|
||||
start_ts = e->ts_us;
|
||||
printf("%-9.3f ", (e->ts_us - start_ts) / 1000000.0);
|
||||
printf("%-9.3f ", (e->ts_us - start_ts) / 1000000.0);
|
||||
}
|
||||
if (e->af == AF_INET) {
|
||||
s.x4.s_addr = e->saddr_v4;
|
||||
@@ -652,18 +652,18 @@ cleanup:
|
||||
memcpy(&s.x6.s6_addr, e->saddr_v6, sizeof(s.x6.s6_addr));
|
||||
memcpy(&d.x6.s6_addr, e->daddr_v6, sizeof(d.x6.s6_addr));
|
||||
} else {
|
||||
fprintf(stderr, "broken event: event->af=%d", e->af);
|
||||
fprintf(stderr, "broken event: event->af=%d", e->af);
|
||||
return;
|
||||
}
|
||||
|
||||
if (env.lport) {
|
||||
printf("%-6d %-12.12s %-2d %-16s %-6d %-16s %-5d %.2f\n", e->tgid,
|
||||
printf("%-6d %-12.12s %-2d %-16s %-6d %-16s %-5d %.2f\n", e->tgid,
|
||||
e->comm, e->af == AF_INET ? 4 : 6,
|
||||
inet_ntop(e->af, &s, src, sizeof(src)), e->lport,
|
||||
inet_ntop(e->af, &d, dst, sizeof(dst)), ntohs(e->dport),
|
||||
e->delta_us / 1000.0);
|
||||
} else {
|
||||
printf("%-6d %-12.12s %-2d %-16s %-16s %-5d %.2f\n", e->tgid, e->comm,
|
||||
printf("%-6d %-12.12s %-2d %-16s %-16s %-5d %.2f\n", e->tgid, e->comm,
|
||||
e->af == AF_INET ? 4 : 6, inet_ntop(e->af, &s, src, sizeof(src)),
|
||||
inet_ntop(e->af, &d, dst, sizeof(dst)), ntohs(e->dport),
|
||||
e->delta_us / 1000.0);
|
||||
|
||||
@@ -200,29 +200,29 @@ struct {
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u16);
|
||||
__type(value, __u16);
|
||||
} sports SEC(".maps");
|
||||
} sports SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u16);
|
||||
__type(value, __u16);
|
||||
} dports SEC(".maps");
|
||||
} dports SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, struct sock *);
|
||||
__type(value, __u64);
|
||||
} timestamps SEC(".maps");
|
||||
} timestamps SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("tracepoint/sock/inet_sock_set_state")
|
||||
SEC("tracepoint/sock/inet_sock_set_state")
|
||||
int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)ctx->skaddr;
|
||||
@@ -299,8 +299,8 @@ int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
if (emit_timestamp) {
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%8s ", ts);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%8s ", ts);
|
||||
}
|
||||
|
||||
inet_ntop(e->family, &e->saddr, saddr, sizeof(saddr));
|
||||
@@ -308,14 +308,14 @@ int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
if (wide_output) {
|
||||
family = e->family == AF_INET ? 4 : 6;
|
||||
printf(
|
||||
"%-16llx %-7d %-16s %-2d %-26s %-5d %-26s %-5d %-11s -> %-11s "
|
||||
"%.3f\n",
|
||||
"%-16llx %-7d %-16s %-2d %-26s %-5d %-26s %-5d %-11s -> %-11s "
|
||||
"%.3f\n",
|
||||
e->skaddr, e->pid, e->task, family, saddr, e->sport, daddr,
|
||||
e->dport, tcp_states[e->oldstate], tcp_states[e->newstate],
|
||||
(double)e->delta_us / 1000);
|
||||
} else {
|
||||
printf(
|
||||
"%-16llx %-7d %-10.10s %-15s %-5d %-15s %-5d %-11s -> %-11s %.3f\n",
|
||||
"%-16llx %-7d %-10.10s %-15s %-5d %-15s %-5d %-11s -> %-11s %.3f\n",
|
||||
e->skaddr, e->pid, e->task, saddr, e->sport, daddr, e->dport,
|
||||
tcp_states[e->oldstate], tcp_states[e->newstate],
|
||||
(double)e->delta_us / 1000);
|
||||
@@ -335,17 +335,17 @@ int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
<h3 id="tcprtt"><a class="header" href="#tcprtt">tcprtt</a></h3>
|
||||
<p>在本章节中,我们将分析<code>tcprtt</code> eBPF 程序的内核态代码。<code>tcprtt</code>是一个用于测量 TCP 往返时间(Round Trip Time, RTT)的程序,它将 RTT 的信息统计到一个 histogram 中。</p>
|
||||
<pre><code class="language-c">
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u64);
|
||||
__type(value, struct hist);
|
||||
} hists SEC(".maps");
|
||||
} hists SEC(".maps");
|
||||
|
||||
static struct hist zero;
|
||||
|
||||
SEC("fentry/tcp_rcv_established")
|
||||
SEC("fentry/tcp_rcv_established")
|
||||
int BPF_PROG(tcp_rcv, struct sock *sk)
|
||||
{
|
||||
const struct inet_sock *inet = (struct inet_sock *)(sk);
|
||||
|
||||
@@ -182,8 +182,8 @@
|
||||
<p>在用户层面进行动态跟踪,即用户级动态跟踪(User-Level Dynamic Tracing)允许我们对任何用户级别的代码进行插桩。比如,我们可以通过在 MySQL 服务器的 <code>dispatch_command()</code> 函数上进行插桩,来跟踪服务器的查询请求:</p>
|
||||
<pre><code class="language-bash"># ./uprobe 'p:cmd /opt/bin/mysqld:_Z16dispatch_command19enum_server_commandP3THDPcj +0(%dx):string'
|
||||
Tracing uprobe cmd (p:cmd /opt/bin/mysqld:0x2dbd40 +0(%dx):string). Ctrl-C to end.
|
||||
mysqld-2855 [001] d... 19957757.590926: cmd: (0x6dbd40) arg1="show tables"
|
||||
mysqld-2855 [001] d... 19957759.703497: cmd: (0x6dbd40) arg1="SELECT * FROM numbers"
|
||||
mysqld-2855 [001] d... 19957757.590926: cmd: (0x6dbd40) arg1="show tables"
|
||||
mysqld-2855 [001] d... 19957759.703497: cmd: (0x6dbd40) arg1="SELECT * FROM numbers"
|
||||
[...]
|
||||
</code></pre>
|
||||
<p>这里我们使用了 <code>uprobe</code> 工具,它利用了 Linux 的内置功能:ftrace(跟踪器)和 uprobes(用户级动态跟踪,需要较新的 Linux 版本,例如 4.0 左右)。其他的跟踪器,如 perf_events 和 SystemTap,也可以实现此功能。</p>
|
||||
@@ -200,7 +200,7 @@ adjust_time_range
|
||||
</code></pre>
|
||||
<p>这有 21,000 个函数。我们也可以跟踪库函数,甚至是单个的指令偏移。</p>
|
||||
<p>用户级动态跟踪的能力是非常强大的,它可以解决无数的问题。然而,使用它也有一些困难:需要确定需要跟踪的代码,处理函数参数,以及应对代码的更改。</p>
|
||||
<p>用户级静态定义跟踪(User-level Statically Defined Tracing, USDT)则可以在某种程度上解决这些问题。USDT 探针(或者称为用户级 "marker")是开发者在代码的关键位置插入的跟踪宏,提供稳定且已经过文档说明的 API。这使得跟踪工作变得更加简单。</p>
|
||||
<p>用户级静态定义跟踪(User-level Statically Defined Tracing, USDT)则可以在某种程度上解决这些问题。USDT 探针(或者称为用户级 "marker")是开发者在代码的关键位置插入的跟踪宏,提供稳定且已经过文档说明的 API。这使得跟踪工作变得更加简单。</p>
|
||||
<p>使用 USDT,我们可以简单地跟踪一个名为 <code>mysql:query__start</code> 的探针,而不是去跟踪那个名为 <code>_Z16dispatch_command19enum_server_commandP3THDPcj</code> 的 C++ 符号,也就是 <code>dispatch_command()</code> 函数。当然,我们仍然可以在需要的时候去跟踪 <code>dispatch_command()</code> 以及其他 21,000 个 mysqld 函数,但只有当 USDT 探针无法解决问题的时候我们才需要这么做。</p>
|
||||
<p>在 Linux 中的 USDT,无论是哪种形式的静态跟踪点,其实都已经存在了几十年。它最近由于 Sun 的 DTrace 工具的流行而再次受到关注,这使得许多常见的应用程序,包括 MySQL、PostgreSQL、Node.js、Java 等都加入了 USDT。SystemTap 则开发了一种可以消费这些 DTrace 探针的方式。</p>
|
||||
<p>你可能正在运行一个已经包含了 USDT 探针的 Linux 应用程序,或者可能需要重新编译(通常是 --enable-dtrace)。你可以使用 <code>readelf</code> 来进行检查,例如对于 Node.js:</p>
|
||||
@@ -221,7 +221,7 @@ Notes at offset 0x00c43058 with length 0x00000494:
|
||||
Arguments: 8@%rax 8@%rdx 8@-136(%rbp) -4@-140(%rbp) 8@-72(%rbp) 8@-80(%rbp) -4@-144(%rbp)
|
||||
[...]
|
||||
</code></pre>
|
||||
<p>这就是使用 --enable-dtrace 重新编译的 node,以及安装了提供 "dtrace" 功能来构建 USDT 支持的 systemtap-sdt-dev 包。这里显示了两个探针:node:gc__start(开始进行垃圾回收)和 node:http__client__request。</p>
|
||||
<p>这就是使用 --enable-dtrace 重新编译的 node,以及安装了提供 "dtrace" 功能来构建 USDT 支持的 systemtap-sdt-dev 包。这里显示了两个探针:node:gc__start(开始进行垃圾回收)和 node:http__client__request。</p>
|
||||
<p>在这一点上,你可以使用 SystemTap 或者 LTTng 来跟踪这些探针。然而,内置的 Linux 跟踪器,比如 ftrace 和 perf_events,目前还无法做到这一点(尽管 perf_events 的支持正在开发中)。</p>
|
||||
<p>USDT 在内核态 eBPF 运行时,也可能产生比较大的性能开销,这时候也可以考虑使用用户态 eBPF 运行时,例如 <a href="https://github.com/eunomia-bpf/bpftime">bpftime</a>。bpftime 是一个基于 LLVM JIT/AOT 的用户态 eBPF 运行时,它可以在用户态运行 eBPF 程序,和内核态的 eBPF 兼容,避免了内核态和用户态之间的上下文切换,从而提高了 eBPF 程序的执行效率。对于 uprobe 而言,bpftime 的性能开销比 kernel 小一个数量级。</p>
|
||||
<h2 id="java-gc-介绍"><a class="header" href="#java-gc-介绍">Java GC 介绍</a></h2>
|
||||
@@ -236,20 +236,20 @@ Notes at offset 0x00c43058 with length 0x00000494:
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/usdt.bpf.h>
|
||||
#include "javagc.h"
|
||||
#include "javagc.h"
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 100);
|
||||
__type(key, uint32_t);
|
||||
__type(value, struct data_t);
|
||||
} data_map SEC(".maps");
|
||||
} data_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__type(key, int);
|
||||
__type(value, int);
|
||||
} perf_map SEC(".maps");
|
||||
} perf_map SEC(".maps");
|
||||
|
||||
__u32 time;
|
||||
|
||||
@@ -286,38 +286,38 @@ static int gc_end(struct pt_regs *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("usdt")
|
||||
SEC("usdt")
|
||||
int handle_gc_start(struct pt_regs *ctx)
|
||||
{
|
||||
return gc_start(ctx);
|
||||
}
|
||||
|
||||
SEC("usdt")
|
||||
SEC("usdt")
|
||||
int handle_gc_end(struct pt_regs *ctx)
|
||||
{
|
||||
return gc_end(ctx);
|
||||
}
|
||||
|
||||
SEC("usdt")
|
||||
SEC("usdt")
|
||||
int handle_mem_pool_gc_start(struct pt_regs *ctx)
|
||||
{
|
||||
return gc_start(ctx);
|
||||
}
|
||||
|
||||
SEC("usdt")
|
||||
SEC("usdt")
|
||||
int handle_mem_pool_gc_end(struct pt_regs *ctx)
|
||||
{
|
||||
return gc_end(ctx);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
</code></pre>
|
||||
<p>首先,我们定义了两个映射(map):</p>
|
||||
<ul>
|
||||
<li><code>data_map</code>:这个 hashmap 存储每个进程 ID 的垃圾收集开始时间。<code>data_t</code> 结构体包含进程 ID、CPU ID 和时间戳。</li>
|
||||
<li><code>perf_map</code>:这是一个 perf event array,用于将数据发送回用户态程序。</li>
|
||||
</ul>
|
||||
<p>然后,我们有四个处理函数:<code>gc_start</code>、<code>gc_end</code> 和两个 USDT 处理函数 <code>handle_mem_pool_gc_start</code> 和 <code>handle_mem_pool_gc_end</code>。这些函数都用 BPF 的 <code>SEC("usdt")</code> 宏注解,以便在 Java 进程中捕获到与垃圾收集相关的 USDT 事件。</p>
|
||||
<p>然后,我们有四个处理函数:<code>gc_start</code>、<code>gc_end</code> 和两个 USDT 处理函数 <code>handle_mem_pool_gc_start</code> 和 <code>handle_mem_pool_gc_end</code>。这些函数都用 BPF 的 <code>SEC("usdt")</code> 宏注解,以便在 Java 进程中捕获到与垃圾收集相关的 USDT 事件。</p>
|
||||
<p><code>gc_start</code> 函数在垃圾收集开始时被调用。它首先获取当前的 CPU ID、进程 ID 和时间戳,然后将这些数据存入 <code>data_map</code>。</p>
|
||||
<p><code>gc_end</code> 函数在垃圾收集结束时被调用。它执行与 <code>gc_start</code> 类似的操作,但是它还从 <code>data_map</code> 中检索开始时间,并计算垃圾收集的持续时间。如果持续时间超过了设定的阈值(变量 <code>time</code>),那么它将数据发送回用户态程序。</p>
|
||||
<p><code>handle_gc_start</code> 和 <code>handle_gc_end</code> 是针对垃圾收集开始和结束事件的处理函数,它们分别调用了 <code>gc_start</code> 和 <code>gc_end</code>。</p>
|
||||
@@ -333,17 +333,17 @@ char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
FILE *f;
|
||||
int i = 0;
|
||||
|
||||
sprintf(buf, "/proc/%d/maps", env.pid);
|
||||
f = fopen(buf, "r");
|
||||
sprintf(buf, "/proc/%d/maps", env.pid);
|
||||
f = fopen(buf, "r");
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
while (fscanf(f, "%zx-%zx %s %zx %*s %*d%[^\n]\n",
|
||||
while (fscanf(f, "%zx-%zx %s %zx %*s %*d%[^\n]\n",
|
||||
&seg_start, &seg_end, mode, &seg_off, line) == 5) {
|
||||
i = 0;
|
||||
while (isblank(line[i]))
|
||||
i++;
|
||||
if (strstr(line + i, "libjvm.so")) {
|
||||
if (strstr(line + i, "libjvm.so")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -356,34 +356,34 @@ char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
</code></pre>
|
||||
<p>接下来,我们看到的是将 eBPF 程序(函数 <code>handle_gc_start</code> 和 <code>handle_gc_end</code>)附加到Java进程的相关USDT探针上。每个程序都通过调用 <code>bpf_program__attach_usdt</code> 函数来实现这一点,该函数的参数包括BPF程序、进程ID、二进制路径以及探针的提供者和名称。如果探针挂载成功,<code>bpf_program__attach_usdt</code> 将返回一个链接对象,该对象将存储在skeleton的链接成员中。如果挂载失败,程序将打印错误消息并进行清理。</p>
|
||||
<pre><code class="language-c"> skel->links.handle_mem_pool_gc_start = bpf_program__attach_usdt(skel->progs.handle_gc_start, env.pid,
|
||||
binary_path, "hotspot", "mem__pool__gc__begin", NULL);
|
||||
binary_path, "hotspot", "mem__pool__gc__begin", NULL);
|
||||
if (!skel->links.handle_mem_pool_gc_start) {
|
||||
err = errno;
|
||||
fprintf(stderr, "attach usdt mem__pool__gc__begin failed: %s\n", strerror(err));
|
||||
fprintf(stderr, "attach usdt mem__pool__gc__begin failed: %s\n", strerror(err));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
skel->links.handle_mem_pool_gc_end = bpf_program__attach_usdt(skel->progs.handle_gc_end, env.pid,
|
||||
binary_path, "hotspot", "mem__pool__gc__end", NULL);
|
||||
binary_path, "hotspot", "mem__pool__gc__end", NULL);
|
||||
if (!skel->links.handle_mem_pool_gc_end) {
|
||||
err = errno;
|
||||
fprintf(stderr, "attach usdt mem__pool__gc__end failed: %s\n", strerror(err));
|
||||
fprintf(stderr, "attach usdt mem__pool__gc__end failed: %s\n", strerror(err));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
skel->links.handle_gc_start = bpf_program__attach_usdt(skel->progs.handle_gc_start, env.pid,
|
||||
binary_path, "hotspot", "gc__begin", NULL);
|
||||
binary_path, "hotspot", "gc__begin", NULL);
|
||||
if (!skel->links.handle_gc_start) {
|
||||
err = errno;
|
||||
fprintf(stderr, "attach usdt gc__begin failed: %s\n", strerror(err));
|
||||
fprintf(stderr, "attach usdt gc__begin failed: %s\n", strerror(err));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
skel->links.handle_gc_end = bpf_program__attach_usdt(skel->progs.handle_gc_end, env.pid,
|
||||
binary_path, "hotspot", "gc__end", NULL);
|
||||
binary_path, "hotspot", "gc__end", NULL);
|
||||
if (!skel->links.handle_gc_end) {
|
||||
err = errno;
|
||||
fprintf(stderr, "attach usdt gc__end failed: %s\n", strerror(err));
|
||||
fprintf(stderr, "attach usdt gc__end failed: %s\n", strerror(err));
|
||||
goto cleanup;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -397,8 +397,8 @@ char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%-8s %-7d %-7d %-7lld\n", ts, e->cpu, e->pid, e->ts/1000);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%-8s %-7d %-7d %-7lld\n", ts, e->cpu, e->pid, e->ts/1000);
|
||||
}
|
||||
</code></pre>
|
||||
<h2 id="安装依赖"><a class="header" href="#安装依赖">安装依赖</a></h2>
|
||||
|
||||
@@ -231,7 +231,7 @@ union combined_alloc_info {
|
||||
<p>这里定义了两个主要的数据结构:<code>alloc_info</code> 和 <code>combined_alloc_info</code>。</p>
|
||||
<p><code>alloc_info</code> 结构体包含了一个内存分配的基本信息,包括分配的内存大小 <code>size</code>、分配发生时的时间戳 <code>timestamp_ns</code>,以及触发分配的调用堆栈 ID <code>stack_id</code>。</p>
|
||||
<p><code>combined_alloc_info</code> 是一个联合体(union),它包含一个嵌入的结构体和一个 <code>__u64</code> 类型的位图表示 <code>bits</code>。嵌入的结构体有两个成员:<code>total_size</code> 和 <code>number_of_allocs</code>,分别代表所有未释放分配的总大小和总次数。其中 40 和 24 分别表示 total_size 和 number_of_allocs这两个成员变量所占用的位数,用来限制其大小。通过这样的位数限制,可以节省combined_alloc_info结构的存储空间。同时,由于total_size和number_of_allocs在存储时是共用一个unsigned long long类型的变量bits,因此可以通过在成员变量bits上进行位运算来访问和修改total_size和number_of_allocs,从而避免了在程序中定义额外的变量和函数的复杂性。</p>
|
||||
<p>接下来,<code>memleak</code> 定义了一系列用于保存内存分配信息和分析结果的 eBPF 映射(maps)。这些映射都以 <code>SEC(".maps")</code> 的形式定义,表示它们属于 eBPF 程序的映射部分。</p>
|
||||
<p>接下来,<code>memleak</code> 定义了一系列用于保存内存分配信息和分析结果的 eBPF 映射(maps)。这些映射都以 <code>SEC(".maps")</code> 的形式定义,表示它们属于 eBPF 程序的映射部分。</p>
|
||||
<pre><code class="language-c">const volatile size_t min_size = 0;
|
||||
const volatile size_t max_size = -1;
|
||||
const volatile size_t page_size = 4096;
|
||||
@@ -245,33 +245,33 @@ struct {
|
||||
__type(key, pid_t);
|
||||
__type(value, u64);
|
||||
__uint(max_entries, 10240);
|
||||
} sizes SEC(".maps");
|
||||
} sizes SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, u64); /* address */
|
||||
__type(value, struct alloc_info);
|
||||
__uint(max_entries, ALLOCS_MAX_ENTRIES);
|
||||
} allocs SEC(".maps");
|
||||
} allocs SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, u64); /* stack id */
|
||||
__type(value, union combined_alloc_info);
|
||||
__uint(max_entries, COMBINED_ALLOCS_MAX_ENTRIES);
|
||||
} combined_allocs SEC(".maps");
|
||||
} combined_allocs SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, u64);
|
||||
__type(value, u64);
|
||||
__uint(max_entries, 10240);
|
||||
} memptrs SEC(".maps");
|
||||
} memptrs SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
|
||||
__type(key, u32);
|
||||
} stack_traces SEC(".maps");
|
||||
} stack_traces SEC(".maps");
|
||||
|
||||
static union combined_alloc_info initial_cinfo;
|
||||
</code></pre>
|
||||
@@ -285,24 +285,24 @@ static union combined_alloc_info initial_cinfo;
|
||||
<li><code>stack_traces</code>:这是一个堆栈追踪类型的映射,键为 <code>u32</code> 类型,用于存储堆栈 ID。</li>
|
||||
</ol>
|
||||
<p>以用户态的内存分配追踪部分为例,主要是挂钩内存相关的函数调用,如 <code>malloc</code>, <code>free</code>, <code>calloc</code>, <code>realloc</code>, <code>mmap</code> 和 <code>munmap</code>,以便在调用这些函数时进行数据记录。在用户态,<code>memleak</code> 主要使用了 uprobes 技术进行挂载。</p>
|
||||
<p>每个函数调用被分为 "enter" 和 "exit" 两部分。"enter" 部分记录的是函数调用的参数,如分配的大小或者释放的地址。"exit" 部分则主要用于获取函数的返回值,如分配得到的内存地址。</p>
|
||||
<p>每个函数调用被分为 "enter" 和 "exit" 两部分。"enter" 部分记录的是函数调用的参数,如分配的大小或者释放的地址。"exit" 部分则主要用于获取函数的返回值,如分配得到的内存地址。</p>
|
||||
<p>这里,<code>gen_alloc_enter</code>, <code>gen_alloc_exit</code>, <code>gen_free_enter</code> 是实现记录行为的函数,他们分别用于记录分配开始、分配结束和释放开始的相关信息。</p>
|
||||
<p>函数原型示例如下:</p>
|
||||
<pre><code class="language-c">SEC("uprobe")
|
||||
<pre><code class="language-c">SEC("uprobe")
|
||||
int BPF_KPROBE(malloc_enter, size_t size)
|
||||
{
|
||||
// 记录分配开始的相关信息
|
||||
return gen_alloc_enter(size);
|
||||
}
|
||||
|
||||
SEC("uretprobe")
|
||||
SEC("uretprobe")
|
||||
int BPF_KRETPROBE(malloc_exit)
|
||||
{
|
||||
// 记录分配结束的相关信息
|
||||
return gen_alloc_exit(ctx);
|
||||
}
|
||||
|
||||
SEC("uprobe")
|
||||
SEC("uprobe")
|
||||
int BPF_KPROBE(free_enter, void *address)
|
||||
{
|
||||
// 记录释放开始的相关信息
|
||||
@@ -326,12 +326,12 @@ int BPF_KPROBE(free_enter, void *address)
|
||||
bpf_map_update_elem(&sizes, &pid, &size, BPF_ANY);
|
||||
|
||||
if (trace_all)
|
||||
bpf_printk("alloc entered, size = %lu\n", size);
|
||||
bpf_printk("alloc entered, size = %lu\n", size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("uprobe")
|
||||
SEC("uprobe")
|
||||
int BPF_KPROBE(malloc_enter, size_t size)
|
||||
{
|
||||
return gen_alloc_enter(size);
|
||||
@@ -370,7 +370,7 @@ int BPF_KPROBE(malloc_enter, size_t size)
|
||||
}
|
||||
|
||||
if (trace_all) {
|
||||
bpf_printk("alloc exited, size = %lu, result = %lx\n",
|
||||
bpf_printk("alloc exited, size = %lu, result = %lx\n",
|
||||
info.size, address);
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ static int gen_alloc_exit(struct pt_regs *ctx)
|
||||
return gen_alloc_exit2(ctx, PT_REGS_RC(ctx));
|
||||
}
|
||||
|
||||
SEC("uretprobe")
|
||||
SEC("uretprobe")
|
||||
int BPF_KRETPROBE(malloc_exit)
|
||||
{
|
||||
return gen_alloc_exit(ctx);
|
||||
@@ -421,7 +421,7 @@ int BPF_KRETPROBE(malloc_exit)
|
||||
|
||||
existing_cinfo = bpf_map_lookup_elem(&combined_allocs, &stack_id);
|
||||
if (!existing_cinfo) {
|
||||
bpf_printk("failed to lookup combined allocs\n");
|
||||
bpf_printk("failed to lookup combined allocs\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -446,14 +446,14 @@ int BPF_KRETPROBE(malloc_exit)
|
||||
update_statistics_del(info->stack_id, info->size);
|
||||
|
||||
if (trace_all) {
|
||||
bpf_printk("free entered, address = %lx, size = %lu\n",
|
||||
bpf_printk("free entered, address = %lx, size = %lu\n",
|
||||
address, info->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("uprobe")
|
||||
SEC("uprobe")
|
||||
int BPF_KPROBE(free_enter, void *address)
|
||||
{
|
||||
return gen_free_enter(address);
|
||||
@@ -461,7 +461,7 @@ int BPF_KPROBE(free_enter, void *address)
|
||||
</code></pre>
|
||||
<p>接下来看 <code>gen_free_enter</code> 函数。它接收一个地址作为参数,这个地址是内存分配的结果,也就是将要释放的内存的起始地址。函数首先在 <code>allocs</code> 这个 map 中使用这个地址作为键来查找对应的 <code>alloc_info</code> 结构体。如果找不到,那么就直接返回,因为这意味着这个地址并没有被分配过。如果找到了,那么就删除这个元素,并且调用 <code>update_statistics_del</code> 函数来更新统计数据。最后,如果启用了全局追踪,那么还会输出一条信息,包括这个地址以及它的大小。
|
||||
在我们追踪和统计内存分配的同时,我们也需要对内核态的内存分配和释放进行追踪。在Linux内核中,kmem_cache_alloc函数和kfree函数分别用于内核态的内存分配和释放。</p>
|
||||
<pre><code class="language-c">SEC("tracepoint/kmem/kfree")
|
||||
<pre><code class="language-c">SEC("tracepoint/kmem/kfree")
|
||||
int memleak__kfree(void *ctx)
|
||||
{
|
||||
const void *ptr;
|
||||
@@ -478,7 +478,7 @@ int memleak__kfree(void *ctx)
|
||||
}
|
||||
</code></pre>
|
||||
<p>上述代码片段定义了一个函数memleak__kfree,这是一个bpf程序,会在内核调用kfree函数时执行。首先,该函数检查是否存在kfree函数。如果存在,则会读取传递给kfree函数的参数(即要释放的内存块的地址),并保存到变量ptr中;否则,会读取传递给kmem_free函数的参数(即要释放的内存块的地址),并保存到变量ptr中。接着,该函数会调用之前定义的gen_free_enter函数来处理该内存块的释放。</p>
|
||||
<pre><code class="language-c">SEC("tracepoint/kmem/kmem_cache_alloc")
|
||||
<pre><code class="language-c">SEC("tracepoint/kmem/kmem_cache_alloc")
|
||||
int memleak__kmem_cache_alloc(struct trace_event_raw_kmem_alloc *ctx)
|
||||
{
|
||||
if (wa_missing_free)
|
||||
|
||||
@@ -227,9 +227,9 @@ sda 0 100 1 4
|
||||
<pre><code class="language-c">#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "biopattern.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "core_fixes.bpf.h"
|
||||
#include "biopattern.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "core_fixes.bpf.h"
|
||||
|
||||
const volatile bool filter_dev = false;
|
||||
const volatile __u32 targ_dev = 0;
|
||||
@@ -239,9 +239,9 @@ struct {
|
||||
__uint(max_entries, 64);
|
||||
__type(key, u32);
|
||||
__type(value, struct counter);
|
||||
} counters SEC(".maps");
|
||||
} counters SEC(".maps");
|
||||
|
||||
SEC("tracepoint/block/block_rq_complete")
|
||||
SEC("tracepoint/block/block_rq_complete")
|
||||
int handle__block_rq_complete(void *args)
|
||||
{
|
||||
struct counter *counterp, zero = {};
|
||||
@@ -278,7 +278,7 @@ int handle__block_rq_complete(void *args)
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<ol>
|
||||
<li>全局变量定义</li>
|
||||
@@ -293,11 +293,11 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
__uint(max_entries, 64);
|
||||
__type(key, u32);
|
||||
__type(value, struct counter);
|
||||
} counters SEC(".maps");
|
||||
} counters SEC(".maps");
|
||||
</code></pre>
|
||||
<p>这部分代码定义了一个 BPF map,类型为哈希表。该映射的键是设备的标识符,而值是一个 <code>counter</code> 结构体,用于存储设备的 I/O 统计信息。</p>
|
||||
<p>追踪点函数:</p>
|
||||
<pre><code class="language-c"> SEC("tracepoint/block/block_rq_complete")
|
||||
<pre><code class="language-c"> SEC("tracepoint/block/block_rq_complete")
|
||||
int handle__block_rq_complete(void *args)
|
||||
{
|
||||
struct counter *counterp, zero = {};
|
||||
@@ -407,7 +407,7 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
|
||||
err = bpf_map_lookup_elem(fd, &next_key, &counter);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "failed to lookup counters: %d\n", err);
|
||||
fprintf(stderr, "failed to lookup counters: %d\n", err);
|
||||
return -1;
|
||||
}
|
||||
lookup_key = next_key;
|
||||
@@ -417,12 +417,12 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
if (env.timestamp) {
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%-9s ", ts);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%-9s ", ts);
|
||||
}
|
||||
partition = partitions__get_by_dev(partitions, next_key);
|
||||
printf("%-7s %5ld %5ld %8d %10lld\n",
|
||||
partition ? partition->name : "Unknown",
|
||||
printf("%-7s %5ld %5ld %8d %10lld\n",
|
||||
partition ? partition->name : "Unknown",
|
||||
counter.random * 100L / total,
|
||||
counter.sequential * 100L / total, total,
|
||||
counter.bytes / 1024);
|
||||
@@ -432,7 +432,7 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) {
|
||||
err = bpf_map_delete_elem(fd, &next_key);
|
||||
if (err < 0) {
|
||||
fprintf(stderr, "failed to cleanup counters: %d\n", err);
|
||||
fprintf(stderr, "failed to cleanup counters: %d\n", err);
|
||||
return -1;
|
||||
}
|
||||
lookup_key = next_key;
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
<p>随着微秒级 NVMe 存储设备的出现,Linux 内核存储堆栈开销变得显著,几乎使访问时间翻倍。我们介绍了 XRP,一个框架,允许应用程序从 eBPF 在 NVMe 驱动程序中的钩子执行用户定义的存储功能,如索引查找或聚合,安全地绕过大部分内核的存储堆栈。为了保持文件系统的语义,XRP 将少量的内核状态传播到其 NVMe 驱动程序钩子,在那里调用用户注册的 eBPF 函数。我们展示了如何利用 XRP 显著提高两个键值存储,BPF-KV,一个简单的 B+ 树键值存储,和 WiredTiger,一个流行的日志结构合并树存储引擎的吞吐量和延迟。</p>
|
||||
<p>OSDI '22 最佳论文: <a href="https://www.usenix.org/conference/osdi22/presentation/zhong">https://www.usenix.org/conference/osdi22/presentation/zhong</a></p>
|
||||
<h2 id="specification-and-verification-in-the-field-applying-formal-methods-to-bpf-just-in-time-compilers-in-the-linux-kernel"><a class="header" href="#specification-and-verification-in-the-field-applying-formal-methods-to-bpf-just-in-time-compilers-in-the-linux-kernel">Specification and verification in the field: Applying formal methods to BPF just-in-time compilers in the Linux kernel</a></h2>
|
||||
<p>本文描述了我们将形式方法应用于 Linux 内核中的一个关键组件,即 Berkeley 数据包过滤器 (BPF) 虚拟机的即时编译器 ("JIT") 的经验。我们使用 Jitterbug 验证这些 JIT,这是第一个提供 JIT 正确性的精确规范的框架,能够排除实际错误,并提供一个自动化的证明策略,该策略可以扩展到实际实现。使用 Jitterbug,我们设计、实施并验证了一个新的针对 32 位 RISC-V 的 BPF JIT,在五个其他部署的 JIT 中找到并修复了 16 个之前未知的错误,并开发了新的 JIT 优化;所有这些更改都已上传到 Linux 内核。结果表明,在一个大型的、未经验证的系统中,通过仔细设计规范和证明策略,可以构建一个经过验证的组件。</p>
|
||||
<p>本文描述了我们将形式方法应用于 Linux 内核中的一个关键组件,即 Berkeley 数据包过滤器 (BPF) 虚拟机的即时编译器 ("JIT") 的经验。我们使用 Jitterbug 验证这些 JIT,这是第一个提供 JIT 正确性的精确规范的框架,能够排除实际错误,并提供一个自动化的证明策略,该策略可以扩展到实际实现。使用 Jitterbug,我们设计、实施并验证了一个新的针对 32 位 RISC-V 的 BPF JIT,在五个其他部署的 JIT 中找到并修复了 16 个之前未知的错误,并开发了新的 JIT 优化;所有这些更改都已上传到 Linux 内核。结果表明,在一个大型的、未经验证的系统中,通过仔细设计规范和证明策略,可以构建一个经过验证的组件。</p>
|
||||
<p>OSDI 20: <a href="https://www.usenix.org/conference/osdi20/presentation/nelson">https://www.usenix.org/conference/osdi20/presentation/nelson</a></p>
|
||||
<h2 id="λ-io-a-unified-io-stack-for-computational-storage"><a class="header" href="#λ-io-a-unified-io-stack-for-computational-storage">λ-IO: A Unified IO Stack for Computational Storage</a></h2>
|
||||
<p>新兴的计算存储设备为存储内计算提供了一个机会。它减少了主机与设备之间的数据移动开销,从而加速了数据密集型应用程序。在这篇文章中,我们介绍 λ-IO,一个统一的 IO 堆栈,跨主机和设备管理计算和存储资源。我们提出了一套设计 - 接口、运行时和调度 - 来解决三个关键问题。我们在全堆栈软件和硬件环境中实施了 λ-IO,并使用合成和实际应用程序对其</p>
|
||||
@@ -205,7 +205,7 @@
|
||||
<p>FAST23: <a href="https://www.usenix.org/conference/fast23/presentation/yang-zhe">https://www.usenix.org/conference/fast23/presentation/yang-zhe</a></p>
|
||||
<h2 id="extension-framework-for-file-systems-in-user-space"><a class="header" href="#extension-framework-for-file-systems-in-user-space">Extension Framework for File Systems in User space</a></h2>
|
||||
<p>用户文件系统相对于其内核实现提供了许多优势,例如开发的简易性和更好的系统可靠性。然而,它们会导致重大的性能损失。我们观察到现有的用户文件系统框架非常通用;它们由一个位于内核中的最小干预层组成,该层简单地将所有低级请求转发到用户空间。虽然这种设计提供了灵活性,但由于频繁的内核-用户上下文切换,它也严重降低了性能。</p>
|
||||
<p>这项工作介绍了 ExtFUSE,一个用于开发可扩展用户文件系统的框架,该框架还允许应用程序在内核中注册"薄"的专用请求处理程序,以满足其特定的操作需求,同时在用户空间中保留复杂的功能。我们使用两个 FUSE 文件系统对 ExtFUSE 进行评估,结果表明 ExtFUSE 可以通过平均不到几百行的改动来提高用户文件系统的性能。ExtFUSE 可在 GitHub 上找到。</p>
|
||||
<p>这项工作介绍了 ExtFUSE,一个用于开发可扩展用户文件系统的框架,该框架还允许应用程序在内核中注册"薄"的专用请求处理程序,以满足其特定的操作需求,同时在用户空间中保留复杂的功能。我们使用两个 FUSE 文件系统对 ExtFUSE 进行评估,结果表明 ExtFUSE 可以通过平均不到几百行的改动来提高用户文件系统的性能。ExtFUSE 可在 GitHub 上找到。</p>
|
||||
<p>ATC 19: <a href="https://www.usenix.org/conference/atc19/presentation/bijlani">https://www.usenix.org/conference/atc19/presentation/bijlani</a></p>
|
||||
<h2 id="electrode-accelerating-distributed-protocols-with-ebpf"><a class="header" href="#electrode-accelerating-distributed-protocols-with-ebpf">Electrode: Accelerating Distributed Protocols with eBPF</a></h2>
|
||||
<p>在标准的Linux内核网络栈下实现分布式协议可以享受到负载感知的CPU缩放、高兼容性以及强大的安全性和隔离性。但由于过多的用户-内核切换和内核网络栈遍历,其性能较低。我们介绍了Electrode,这是一套为分布式协议设计的基于eBPF的性能优化。这些优化在网络栈之前在内核中执行,但实现了与用户空间中实现的相似功能(例如,消息广播,收集ack的仲裁),从而避免了用户-内核切换和内核网络栈遍历所带来的开销。我们展示,当应用于经典的Multi-Paxos状态机复制协议时,Electrode可以提高其吞吐量高达128.4%,并将延迟降低高达41.7%。</p>
|
||||
@@ -227,9 +227,9 @@
|
||||
<p>在这项工作中,我们研究如何在 eBPF 中开发高性能的网络测量。我们以绘图为案例研究,因为它们具有支持广泛任务的能力,同时提供低内存占用和准确性保证。我们实现了 NitroSketch,一个用于用户空间网络的最先进的绘图,并表明用户空间网络的最佳实践不能直接应用于 eBPF,因为它的性能特点不同。通过应用我们学到的经验教训,我们将其性能提高了40%,与初级实现相比。</p>
|
||||
<p>SIGCOMM 23: <a href="https://dl.acm.org/doi/abs/10.1145/3594255.3594256">https://dl.acm.org/doi/abs/10.1145/3594255.3594256</a></p>
|
||||
<h2 id="spright-extracting-the-server-from-serverless-computing-high-performance-ebpf-based-event-driven-shared-memory-processing"><a class="header" href="#spright-extracting-the-server-from-serverless-computing-high-performance-ebpf-based-event-driven-shared-memory-processing">SPRIGHT: extracting the server from serverless computing! high-performance eBPF-based event-driven, shared-memory processing</a></h2>
|
||||
<p>无服务器计算在云环境中承诺提供高效、低成本的计算能力。然而,现有的解决方案,如Knative这样的开源平台,包含了繁重的组件,破坏了无服务器计算的目标。此外,这种无服务器平台缺乏数据平面优化,无法实现高效的、高性能的功能链,这也是流行的微服务开发范式的设施。它们为构建功能链使用的不必要的复杂和重复的功能严重降低了性能。"冷启动"延迟是另一个威慑因素。</p>
|
||||
<p>我们描述了 SPRIGHT,一个轻量级、高性能、响应式的无服务器框架。SPRIGHT 利用共享内存处理显著提高了数据平面的可伸缩性,通过避免不必要的协议处理和序列化-反序列化开销。SPRIGHT 大量利用扩展的伯克利数据包过滤器 (eBPF) 进行事件驱动处理。我们创造性地使用 eBPF 的套接字消息机制支持共享内存处理,其开销严格与负载成正比。与常驻、基于轮询的DPDK相比,SPRIGHT 在真实工作负载下实现了相同的数据平面性能,但 CPU 使用率降低了10倍。此外,eBPF 为 SPRIGHT 带来了好处,替换了繁重的无服务器组件,使我们能够以微不足道的代价保持函数处于"暖"状态。</p>
|
||||
<p>我们的初步实验结果显示,与 Knative 相比,SPRIGHT 在吞吐量和延迟方面实现了一个数量级的提高,同时大大减少了 CPU 使用,并消除了 "冷启动"的需要。</p>
|
||||
<p>无服务器计算在云环境中承诺提供高效、低成本的计算能力。然而,现有的解决方案,如Knative这样的开源平台,包含了繁重的组件,破坏了无服务器计算的目标。此外,这种无服务器平台缺乏数据平面优化,无法实现高效的、高性能的功能链,这也是流行的微服务开发范式的设施。它们为构建功能链使用的不必要的复杂和重复的功能严重降低了性能。"冷启动"延迟是另一个威慑因素。</p>
|
||||
<p>我们描述了 SPRIGHT,一个轻量级、高性能、响应式的无服务器框架。SPRIGHT 利用共享内存处理显著提高了数据平面的可伸缩性,通过避免不必要的协议处理和序列化-反序列化开销。SPRIGHT 大量利用扩展的伯克利数据包过滤器 (eBPF) 进行事件驱动处理。我们创造性地使用 eBPF 的套接字消息机制支持共享内存处理,其开销严格与负载成正比。与常驻、基于轮询的DPDK相比,SPRIGHT 在真实工作负载下实现了相同的数据平面性能,但 CPU 使用率降低了10倍。此外,eBPF 为 SPRIGHT 带来了好处,替换了繁重的无服务器组件,使我们能够以微不足道的代价保持函数处于"暖"状态。</p>
|
||||
<p>我们的初步实验结果显示,与 Knative 相比,SPRIGHT 在吞吐量和延迟方面实现了一个数量级的提高,同时大大减少了 CPU 使用,并消除了 "冷启动"的需要。</p>
|
||||
<p><a href="https://dl.acm.org/doi/10.1145/3544216.3544259">https://dl.acm.org/doi/10.1145/3544216.3544259</a></p>
|
||||
<h2 id="programmable-system-call-security-with-ebpf"><a class="header" href="#programmable-system-call-security-with-ebpf">Programmable System Call Security with eBPF</a></h2>
|
||||
<p>利用 eBPF 进行可编程的系统调用安全</p>
|
||||
|
||||
@@ -198,27 +198,27 @@ CONFIG_BPF_LSM=y
|
||||
ndlock,lockdown,yama,integrity,apparmor
|
||||
</code></pre>
|
||||
<p>查看输出是否包含 bpf 选项,如果输出不包含(像上面的例子),可以通过修改 <code>/etc/default/grub</code>:</p>
|
||||
<pre><code class="language-conf">GRUB_CMDLINE_LINUX="lsm=ndlock,lockdown,yama,integrity,apparmor,bpf"
|
||||
<pre><code class="language-conf">GRUB_CMDLINE_LINUX="lsm=ndlock,lockdown,yama,integrity,apparmor,bpf"
|
||||
</code></pre>
|
||||
<p>并通过 <code>update-grub2</code> 命令更新 grub 配置(不同系统的对应命令可能不同),然后重启系统。</p>
|
||||
<h2 id="编写-ebpf-程序"><a class="header" href="#编写-ebpf-程序">编写 eBPF 程序</a></h2>
|
||||
<pre><code class="language-C">// lsm-connect.bpf.c
|
||||
#include "vmlinux.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
#define EPERM 1
|
||||
#define AF_INET 2
|
||||
|
||||
const __u32 blockme = 16843009; // 1.1.1.1 -> int
|
||||
|
||||
SEC("lsm/socket_connect")
|
||||
SEC("lsm/socket_connect")
|
||||
int BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, int addrlen, int ret)
|
||||
{
|
||||
// Satisfying "cannot override a denial" rule
|
||||
// Satisfying "cannot override a denial" rule
|
||||
if (ret != 0)
|
||||
{
|
||||
return ret;
|
||||
@@ -235,11 +235,11 @@ int BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, in
|
||||
|
||||
// Where do you want to go?
|
||||
__u32 dest = addr->sin_addr.s_addr;
|
||||
bpf_printk("lsm: found connect to %d", dest);
|
||||
bpf_printk("lsm: found connect to %d", dest);
|
||||
|
||||
if (dest == blockme)
|
||||
{
|
||||
bpf_printk("lsm: blocking %d", dest);
|
||||
bpf_printk("lsm: blocking %d", dest);
|
||||
return -EPERM;
|
||||
}
|
||||
return 0;
|
||||
@@ -248,7 +248,7 @@ int BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, in
|
||||
</code></pre>
|
||||
<p>这是一段 C 实现的 eBPF 内核侧代码,它会阻碍所有试图通过 socket 对 1.1.1.1 的连接操作,其中:</p>
|
||||
<ul>
|
||||
<li><code>SEC("lsm/socket_connect")</code> 宏指出该程序期望的挂载点;</li>
|
||||
<li><code>SEC("lsm/socket_connect")</code> 宏指出该程序期望的挂载点;</li>
|
||||
<li>程序通过 <code>BPF_PROG</code> 宏定义(详情可查看 <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/tools/lib/bpf/bpf_tracing.h">tools/lib/bpf/bpf_tracing.h</a>);</li>
|
||||
<li><code>restrict_connect</code> 是 <code>BPF_PROG</code> 宏要求的程序名;</li>
|
||||
<li><code>ret</code> 是该挂载点上(潜在的)当前函数之前的 LSM 检查程序的返回值;</li>
|
||||
@@ -293,7 +293,7 @@ Retrying.
|
||||
</code></pre>
|
||||
<p>完整源代码:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/19-lsm-connect">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/19-lsm-connect</a></p>
|
||||
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
|
||||
<p>本文介绍了如何使用 BPF LSM 来限制通过 socket 对特定 IPv4 地址的访问。我们可以通过修改 GRUB 配置文件来开启 LSM 的 BPF 挂载点。在 eBPF 程序中,我们通过 <code>BPF_PROG</code> 宏定义函数,并通过 <code>SEC</code> 宏指定挂载点;在函数实现上,遵循 LSM 安全检查模块中 "cannot override a denial" 的原则,并根据 socket 连接请求的目的地址对该请求进行限制。</p>
|
||||
<p>本文介绍了如何使用 BPF LSM 来限制通过 socket 对特定 IPv4 地址的访问。我们可以通过修改 GRUB 配置文件来开启 LSM 的 BPF 挂载点。在 eBPF 程序中,我们通过 <code>BPF_PROG</code> 宏定义函数,并通过 <code>SEC</code> 宏指定挂载点;在函数实现上,遵循 LSM 安全检查模块中 "cannot override a denial" 的原则,并根据 socket 连接请求的目的地址对该请求进行限制。</p>
|
||||
<p>如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 或网站 <a href="https://eunomia.dev/zh/tutorials/">https://eunomia.dev/zh/tutorials/</a> 以获取更多示例和完整的教程。</p>
|
||||
<h2 id="参考"><a class="header" href="#参考">参考</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -196,14 +196,14 @@
|
||||
</ol>
|
||||
<h2 id="kprobe-示例"><a class="header" href="#kprobe-示例">kprobe 示例</a></h2>
|
||||
<p>完整代码如下:</p>
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("kprobe/do_unlinkat")
|
||||
SEC("kprobe/do_unlinkat")
|
||||
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
|
||||
{
|
||||
pid_t pid;
|
||||
@@ -211,31 +211,31 @@ int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
filename = BPF_CORE_READ(name, name);
|
||||
bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename);
|
||||
bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kretprobe/do_unlinkat")
|
||||
SEC("kretprobe/do_unlinkat")
|
||||
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret);
|
||||
bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
<p>这段代码是一个简单的 eBPF 程序,用于监测和捕获在 Linux 内核中执行的 unlink 系统调用。unlink 系统调用的功能是删除一个文件,这个 eBPF 程序通过使用 kprobe(内核探针)在<code>do_unlinkat</code>函数的入口和退出处放置钩子,实现对该系统调用的跟踪。</p>
|
||||
<p>首先,我们导入必要的头文件,如 vmlinux.h,bpf_helpers.h,bpf_tracing.h 和 bpf_core_read.h。接着,我们定义许可证,以允许程序在内核中运行。</p>
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
</code></pre>
|
||||
<p>接下来,我们定义一个名为<code>BPF_KPROBE(do_unlinkat)</code>的 kprobe,当进入<code>do_unlinkat</code>函数时,它会被触发。该函数接受两个参数:<code>dfd</code>(文件描述符)和<code>name</code>(文件名结构体指针)。在这个 kprobe 中,我们获取当前进程的 PID(进程标识符),然后读取文件名。最后,我们使用<code>bpf_printk</code>函数在内核日志中打印 PID 和文件名。</p>
|
||||
<pre><code class="language-c">SEC("kprobe/do_unlinkat")
|
||||
<pre><code class="language-c">SEC("kprobe/do_unlinkat")
|
||||
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
|
||||
{
|
||||
pid_t pid;
|
||||
@@ -243,18 +243,18 @@ int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
filename = BPF_CORE_READ(name, name);
|
||||
bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename);
|
||||
bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
<p>接下来,我们定义一个名为<code>BPF_KRETPROBE(do_unlinkat_exit)</code>的 kretprobe,当从<code>do_unlinkat</code>函数退出时,它会被触发。这个 kretprobe 的目的是捕获函数的返回值(ret)。我们再次获取当前进程的 PID,并使用<code>bpf_printk</code>函数在内核日志中打印 PID 和返回值。</p>
|
||||
<pre><code class="language-c">SEC("kretprobe/do_unlinkat")
|
||||
<pre><code class="language-c">SEC("kretprobe/do_unlinkat")
|
||||
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret);
|
||||
bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -189,9 +189,9 @@
|
||||
#define TC_ACT_OK 0
|
||||
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
|
||||
|
||||
/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
|
||||
/// @tcopts {"handle":1, "priority":1}
|
||||
SEC("tc")
|
||||
/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
|
||||
/// @tcopts {"handle":1, "priority":1}
|
||||
SEC("tc")
|
||||
int tc_ingress(struct __sk_buff *ctx)
|
||||
{
|
||||
void *data_end = (void *)(__u64)ctx->data_end;
|
||||
@@ -210,16 +210,16 @@ int tc_ingress(struct __sk_buff *ctx)
|
||||
if ((void *)(l3 + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
bpf_printk("Got IP packet: tot_len: %d, ttl: %d", bpf_ntohs(l3->tot_len), l3->ttl);
|
||||
bpf_printk("Got IP packet: tot_len: %d, ttl: %d", bpf_ntohs(l3->tot_len), l3->ttl);
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
char __license[] SEC("license") = "GPL";
|
||||
char __license[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这段代码定义了一个 eBPF 程序,它可以通过 Linux TC(Transmission Control)来捕获数据包并进行处理。在这个程序中,我们限定了只捕获 IPv4 协议的数据包,然后通过 bpf_printk 函数打印出数据包的总长度和 Time-To-Live(TTL)字段的值。</p>
|
||||
<p>需要注意的是,我们在代码中使用了一些 BPF 库函数,例如 bpf_htons 和 bpf_ntohs 函数,它们用于进行网络字节序和主机字节序之间的转换。此外,我们还使用了一些注释来为 TC 提供附加点和选项信息。例如,在这段代码的开头,我们使用了以下注释:</p>
|
||||
<pre><code class="language-c">/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
|
||||
/// @tcopts {"handle":1, "priority":1}
|
||||
<pre><code class="language-c">/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
|
||||
/// @tcopts {"handle":1, "priority":1}
|
||||
</code></pre>
|
||||
<p>这些注释告诉 TC 将 eBPF 程序附加到网络接口的 ingress 附加点,并指定了 handle 和 priority 选项的值。关于 libbpf 中 tc 相关的 API 可以参考 <a href="https://patchwork.kernel.org/project/netdevbpf/patch/20210512103451.989420-3-memxor@gmail.com/">patchwork</a> 中的介绍。</p>
|
||||
<p>总之,这段代码实现了一个简单的 eBPF 程序,用于捕获数据包并打印出它们的信息。</p>
|
||||
|
||||
@@ -187,33 +187,33 @@
|
||||
<p>除此之外,利用内核模块和内核网络协议栈中的 hook 点也是一种思路,然而前者对内核的改动大,出错的代价高昂;后者在整套包处理流程中位置偏后,其效率不够理想。</p>
|
||||
<p>总而言之,xdp + eBPF 为可编程包处理系统提出了一种更为稳健的思路,在某种程度上权衡了上述方案的种种优点和不足,获取较高性能的同时又不会对内核的包处理流程进行过多的改变,同时借助 eBPF 虚拟机的优势将用户定义的包处理过程进行隔离和限制,提高了安全性。</p>
|
||||
<h2 id="编写-ebpf-程序"><a class="header" href="#编写-ebpf-程序">编写 eBPF 程序</a></h2>
|
||||
<pre><code class="language-C">#include "vmlinux.h"
|
||||
<pre><code class="language-C">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
/// @ifindex 1
|
||||
/// @flags 0
|
||||
/// @xdpopts {"old_prog_fd":0}
|
||||
SEC("xdp")
|
||||
/// @xdpopts {"old_prog_fd":0}
|
||||
SEC("xdp")
|
||||
int xdp_pass(struct xdp_md* ctx) {
|
||||
void* data = (void*)(long)ctx->data;
|
||||
void* data_end = (void*)(long)ctx->data_end;
|
||||
int pkt_sz = data_end - data;
|
||||
|
||||
bpf_printk("packet size is %d", pkt_sz);
|
||||
bpf_printk("packet size is %d", pkt_sz);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
char __license[] SEC("license") = "GPL";
|
||||
char __license[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这是一段 C 语言实现的 eBPF 内核侧代码,它能够通过 xdp 捕获所有经过目标网络设备的数据包,计算其大小并输出到 <code>trace_pipe</code> 中。</p>
|
||||
<p>值得注意的是,在代码中我们使用了以下注释:</p>
|
||||
<pre><code class="language-C">/// @ifindex 1
|
||||
/// @flags 0
|
||||
/// @xdpopts {"old_prog_fd":0}
|
||||
/// @xdpopts {"old_prog_fd":0}
|
||||
</code></pre>
|
||||
<p>这是由 eunomia-bpf 提供的功能,我们可以通过这样的注释告知 eunomia-bpf 加载器此 xdp 程序想要挂载的目标网络设备编号,挂载的标志和选项。</p>
|
||||
<p>这些变量的设计基于 libbpf 提供的 API,可以通过 <a href="https://patchwork.kernel.org/project/netdevbpf/patch/20220120061422.2710637-2-andrii@kernel.org/#24705508">patchwork</a> 查看接口的详细介绍。</p>
|
||||
<p><code>SEC("xdp")</code> 宏指出 BPF 程序的类型,<code>ctx</code> 是此 BPF 程序执行的上下文,用于包处理流程。</p>
|
||||
<p><code>SEC("xdp")</code> 宏指出 BPF 程序的类型,<code>ctx</code> 是此 BPF 程序执行的上下文,用于包处理流程。</p>
|
||||
<p>在程序的最后,我们返回了 <code>XDP_PASS</code>,这表示我们的 xdp 程序会将经过目标网络设备的包正常交付给内核的网络协议栈。可以通过 <a href="https://prototype-kernel.readthedocs.io/en/latest/networking/XDP/implementation/xdp_actions.html">XDP actions</a> 了解更多 xdp 的处理动作。</p>
|
||||
<h2 id="编译运行"><a class="header" href="#编译运行">编译运行</a></h2>
|
||||
<p>通过容器编译:</p>
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
libbpf: prog 'tracepoint__syscalls__sys_enter_open': failed to create tracepoint 'syscalls/sys_enter_open' perf event: No such file or directory
|
||||
libbpf: prog 'tracepoint__syscalls__sys_enter_open': failed to auto-attach: -2
|
||||
failed to attach skeleton
|
||||
Error: BpfError("load and attach ebpf program failed")
|
||||
Error: BpfError("load and attach ebpf program failed")
|
||||
</code></pre>
|
||||
<p>后经查看发现内核未开启 <code>CONFIG_FTRACE_SYSCALLS</code> 选项,导致无法使用 syscalls 的 tracepoint。</p>
|
||||
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
|
||||
@@ -303,12 +303,10 @@ Error: BpfError("load and attach ebpf program failed")
|
||||
<p>如果希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 或网站 <a href="https://eunomia.dev/zh/tutorials/">https://eunomia.dev/zh/tutorials/</a> 以获取更多示例和完整的教程。</p>
|
||||
<h2 id="参考"><a class="header" href="#参考">参考</a></h2>
|
||||
<div class="footnote-definition" id="Google"><sup class="footnote-definition-label">2</sup>
|
||||
<p><a href="https://source.android.google.cn/docs/core/architecture/kernel/bpf">https://source.android.google.cn/docs/core/architecture/kernel/bpf</a>
|
||||
</p>
|
||||
<p><a href="https://source.android.google.cn/docs/core/architecture/kernel/bpf">https://source.android.google.cn/docs/core/architecture/kernel/bpf</a></p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="WeiShu"><sup class="footnote-definition-label">1</sup>
|
||||
<p><a href="https://mp.weixin.qq.com/s/mul4n5D3nXThjxuHV7GpMA">https://mp.weixin.qq.com/s/mul4n5D3nXThjxuHV7GpMA</a>
|
||||
</p>
|
||||
<p><a href="https://mp.weixin.qq.com/s/mul4n5D3nXThjxuHV7GpMA">https://mp.weixin.qq.com/s/mul4n5D3nXThjxuHV7GpMA</a></p>
|
||||
</div>
|
||||
<div class="footnote-definition" id="SeeFlowerX"><sup class="footnote-definition-label">3</sup>
|
||||
<p><a href="https://blog.seeflower.dev/archives/138/">https://blog.seeflower.dev/archives/138/</a></p>
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
</ol>
|
||||
<p>正如上文所述,eBPF 提供了一个强大的解决方案,允许我们在内核层面捕获和分析七层协议的流量,而无需对应用程序进行任何修改。这种方法为我们提供了一个独特的机会,可以更简单、更高效地处理应用层流量,特别是在微服务和分布式环境中。</p>
|
||||
<p>在处理网络流量和系统行为时,选择在内核态而非用户态进行处理有其独特的优势。首先,内核态处理可以直接访问系统资源和硬件,从而提供更高的性能和效率。其次,由于内核是操作系统的核心部分,它可以提供对系统行为的全面视图,而不受任何用户空间应用程序的限制。</p>
|
||||
<p>**无插桩追踪("zero-instrumentation observability")**的优势如下:</p>
|
||||
<p>**无插桩追踪("zero-instrumentation observability")**的优势如下:</p>
|
||||
<ol>
|
||||
<li><strong>性能开销小</strong>:由于不需要修改或添加额外的代码到应用程序中,所以对性能的影响最小化。</li>
|
||||
<li><strong>透明性</strong>:开发者和运维人员不需要知道应用程序的内部工作原理,也不需要访问源代码。</li>
|
||||
@@ -255,7 +255,7 @@ eBPF 系统调用跟踪通常涉及将 eBPF 程序附加到与系统调用相关
|
||||
<p>总之,eBPF 的 socket filter 和 syscall 追踪都可以用于追踪 HTTP 流量,但 socket filters 更直接且更适合此目的。然而,如果您对应用程序如何与系统交互的更广泛的上下文感兴趣(例如,哪些系统调用导致了 HTTP 流量),那么系统调用跟踪将是非常有价值的。在许多高级的可观察性设置中,这两种工具可能会同时使用,以提供系统和网络行为的全面视图。</p>
|
||||
<h2 id="使用-ebpf-socket-filter-来捕获-http-流量"><a class="header" href="#使用-ebpf-socket-filter-来捕获-http-流量">使用 eBPF socket filter 来捕获 HTTP 流量</a></h2>
|
||||
<p>eBPF 代码由用户态和内核态组成,这里主要关注于内核态代码。这是使用 eBPF socket filter 技术来在内核中捕获HTTP流量的主要逻辑,完整代码如下:</p>
|
||||
<pre><code class="language-c">SEC("socket")
|
||||
<pre><code class="language-c">SEC("socket")
|
||||
int socket_handler(struct __sk_buff *skb)
|
||||
{
|
||||
struct so_event *e;
|
||||
@@ -315,12 +315,12 @@ int socket_handler(struct __sk_buff *skb)
|
||||
return 0;
|
||||
}
|
||||
bpf_skb_load_bytes(skb, payload_offset, line_buffer, 7);
|
||||
bpf_printk("%d len %d buffer: %s", payload_offset, payload_length, line_buffer);
|
||||
if (bpf_strncmp(line_buffer, 3, "GET") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "POST") != 0 &&
|
||||
bpf_strncmp(line_buffer, 3, "PUT") != 0 &&
|
||||
bpf_strncmp(line_buffer, 6, "DELETE") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "HTTP") != 0)
|
||||
bpf_printk("%d len %d buffer: %s", payload_offset, payload_length, line_buffer);
|
||||
if (bpf_strncmp(line_buffer, 3, "GET") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "POST") != 0 &&
|
||||
bpf_strncmp(line_buffer, 3, "PUT") != 0 &&
|
||||
bpf_strncmp(line_buffer, 6, "DELETE") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "HTTP") != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -346,7 +346,7 @@ int socket_handler(struct __sk_buff *skb)
|
||||
}
|
||||
</code></pre>
|
||||
<p>当分析这段eBPF程序时,我们将按照每个代码块的内容来详细解释,并提供相关的背景知识:</p>
|
||||
<pre><code class="language-c">SEC("socket")
|
||||
<pre><code class="language-c">SEC("socket")
|
||||
int socket_handler(struct __sk_buff *skb)
|
||||
{
|
||||
// ...
|
||||
@@ -407,13 +407,13 @@ if (proto != ETH_P_IP)
|
||||
<li><code>frag_off = __bpf_ntohs(frag_off);</code>:将加载的片偏移字段从网络字节序(Big-Endian)转换为主机字节序。网络协议通常使用大端字节序表示数据,而主机可能使用大端或小端字节序。这里将片偏移字段转换为主机字节序,以便进一步处理。</li>
|
||||
<li><code>return frag_off & (IP_MF | IP_OFFSET);</code>:这行代码通过使用位运算检查片偏移字段的值,以确定是否为IP分片。具体来说,它使用位与运算符<code>&</code>将片偏移字段与两个标志位进行位与运算:
|
||||
<ul>
|
||||
<li><code>IP_MF</code>:表示"更多分片"标志(More Fragments)。如果这个标志位被设置为1,表示数据包是分片的一部分,还有更多分片。</li>
|
||||
<li><code>IP_MF</code>:表示"更多分片"标志(More Fragments)。如果这个标志位被设置为1,表示数据包是分片的一部分,还有更多分片。</li>
|
||||
<li><code>IP_OFFSET</code>:表示片偏移字段。如果片偏移字段不为0,表示数据包是分片的一部分,且具有片偏移值。
|
||||
如果这两个标志位中的任何一个被设置为1,那么结果就不为零,说明数据包是IP分片。如果都为零,说明数据包不是分片。</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
<p>需要注意的是,IP头部的片偏移字段以8字节为单位,所以实际的片偏移值需要左移3位来得到字节偏移。此外,IP头部的"更多分片"标志(IP_MF)表示数据包是否有更多的分片,通常与片偏移字段一起使用来指示整个数据包的分片情况。这个函数只关心这两个标志位,如果其中一个标志被设置,就认为是IP分片。</p>
|
||||
<p>需要注意的是,IP头部的片偏移字段以8字节为单位,所以实际的片偏移值需要左移3位来得到字节偏移。此外,IP头部的"更多分片"标志(IP_MF)表示数据包是否有更多的分片,通常与片偏移字段一起使用来指示整个数据包的分片情况。这个函数只关心这两个标志位,如果其中一个标志被设置,就认为是IP分片。</p>
|
||||
<pre><code class="language-c">bpf_skb_load_bytes(skb, ETH_HLEN, &hdr_len, sizeof(hdr_len));
|
||||
hdr_len &= 0x0f;
|
||||
hdr_len *= 4;
|
||||
@@ -479,14 +479,14 @@ if (payload_length < 7 || payload_offset < 0)
|
||||
return 0;
|
||||
}
|
||||
bpf_skb_load_bytes(skb, payload_offset, line_buffer, 7);
|
||||
bpf_printk("%d len %d buffer: %s", payload_offset, payload_length, line_buffer);
|
||||
bpf_printk("%d len %d buffer: %s", payload_offset, payload_length, line_buffer);
|
||||
</code></pre>
|
||||
<p>这部分代码用于加载HTTP请求行的前7个字节,存储在名为<code>line_buffer</code>的字符数组中。然后,它检查HTTP请求数据的长度是否小于7字节或偏移量是否为负数,如果满足这些条件,说明HTTP请求不完整,直接返回0。最后,它使用<code>bpf_printk</code>函数将HTTP请求行的内容打印到内核日志中,以供调试和分析。</p>
|
||||
<pre><code class="language-c">if (bpf_strncmp(line_buffer, 3, "GET") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "POST") != 0 &&
|
||||
bpf_strncmp(line_buffer, 3, "PUT") != 0 &&
|
||||
bpf_strncmp(line_buffer, 6, "DELETE") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "HTTP") != 0)
|
||||
<pre><code class="language-c">if (bpf_strncmp(line_buffer, 3, "GET") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "POST") != 0 &&
|
||||
bpf_strncmp(line_buffer, 3, "PUT") != 0 &&
|
||||
bpf_strncmp(line_buffer, 6, "DELETE") != 0 &&
|
||||
bpf_strncmp(line_buffer, 4, "HTTP") != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -534,7 +534,7 @@ return skb->len;
|
||||
sock = open_raw_sock(interface);
|
||||
if (sock < 0) {
|
||||
err = -2;
|
||||
fprintf(stderr, "Failed to open raw socket\n");
|
||||
fprintf(stderr, "Failed to open raw socket\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ return skb->len;
|
||||
prog_fd = bpf_program__fd(skel->progs.socket_handler);
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd))) {
|
||||
err = -3;
|
||||
fprintf(stderr, "Failed to attach to raw socket\n");
|
||||
fprintf(stderr, "Failed to attach to raw socket\n");
|
||||
goto cleanup;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -567,14 +567,14 @@ $ sudo ./sockfilter
|
||||
<p>在另外一个窗口中,使用 python 启动一个简单的 web server:</p>
|
||||
<pre><code class="language-console">python3 -m http.server
|
||||
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
|
||||
127.0.0.1 - - [18/Sep/2023 01:05:52] "GET / HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [18/Sep/2023 01:05:52] "GET / HTTP/1.1" 200 -
|
||||
</code></pre>
|
||||
<p>可以使用 curl 发起请求:</p>
|
||||
<pre><code class="language-c">$ curl http://0.0.0.0:8000/
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8">
|
||||
<title>Directory listing for /</title>
|
||||
....
|
||||
</code></pre>
|
||||
@@ -598,10 +598,10 @@ Server: SimpleHTTP/0.6 Python/3.11.4
|
||||
__uint(max_entries, 4096);
|
||||
__type(key, u64);
|
||||
__type(value, struct accept_args_t);
|
||||
} active_accept_args_map SEC(".maps");
|
||||
} active_accept_args_map SEC(".maps");
|
||||
|
||||
// 定义在 accept 系统调用入口的追踪点
|
||||
SEC("tracepoint/syscalls/sys_enter_accept")
|
||||
SEC("tracepoint/syscalls/sys_enter_accept")
|
||||
int sys_enter_accept(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
u64 id = bpf_get_current_pid_tgid();
|
||||
@@ -611,7 +611,7 @@ int sys_enter_accept(struct trace_event_raw_sys_enter *ctx)
|
||||
}
|
||||
|
||||
// 定义在 accept 系统调用退出的追踪点
|
||||
SEC("tracepoint/syscalls/sys_exit_accept")
|
||||
SEC("tracepoint/syscalls/sys_exit_accept")
|
||||
int sys_exit_accept(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
// ... 处理 accept 调用的结果
|
||||
@@ -629,10 +629,10 @@ struct
|
||||
__uint(max_entries, 4096);
|
||||
__type(key, u64);
|
||||
__type(value, struct data_args_t);
|
||||
} active_read_args_map SEC(".maps");
|
||||
} active_read_args_map SEC(".maps");
|
||||
|
||||
// 定义在 read 系统调用入口的追踪点
|
||||
SEC("tracepoint/syscalls/sys_enter_read")
|
||||
SEC("tracepoint/syscalls/sys_enter_read")
|
||||
int sys_enter_read(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
// ... 获取和存储 read 调用的参数
|
||||
@@ -662,7 +662,7 @@ static inline void process_data(struct trace_event_raw_sys_exit *ctx,
|
||||
}
|
||||
|
||||
// 定义在 read 系统调用退出的追踪点
|
||||
SEC("tracepoint/syscalls/sys_exit_read")
|
||||
SEC("tracepoint/syscalls/sys_exit_read")
|
||||
int sys_exit_read(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
// ... 处理 read 调用的结果
|
||||
@@ -675,7 +675,7 @@ int sys_exit_read(struct trace_event_raw_sys_exit *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
char _license[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这段代码简要展示了如何使用eBPF追踪Linux内核中的系统调用来捕获HTTP流量。以下是对代码的hook位置和流程的详细解释,以及需要hook哪些系统调用来实现完整的请求追踪:</p>
|
||||
<h3 id="hook-位置和流程"><a class="header" href="#hook-位置和流程"><strong>Hook 位置和流程</strong></a></h3>
|
||||
|
||||
@@ -177,25 +177,25 @@
|
||||
<p>eBPF(扩展的伯克利数据包过滤器)是 Linux 内核中的一个强大功能,可以在无需更改内核源代码或重启内核的情况下,运行、加载和更新用户定义的代码。这种功能让 eBPF 在网络和系统性能分析、数据包过滤、安全策略等方面有了广泛的应用。</p>
|
||||
<p>在本篇教程中,我们将展示如何利用 eBPF 来隐藏进程或文件信息,这是网络安全和防御领域中一种常见的技术。</p>
|
||||
<h2 id="背景知识与实现机制"><a class="header" href="#背景知识与实现机制">背景知识与实现机制</a></h2>
|
||||
<p>"进程隐藏" 能让特定的进程对操作系统的常规检测机制变得不可见。在黑客攻击或系统防御的场景中,这种技术都可能被应用。具体来说,Linux 系统中每个进程都在 /proc/ 目录下有一个以其进程 ID 命名的子文件夹,包含了该进程的各种信息。<code>ps</code> 命令就是通过查找这些文件夹来显示进程信息的。因此,如果我们能隐藏某个进程的 /proc/ 文件夹,就能让这个进程对 <code>ps</code> 命令等检测手段“隐身”。</p>
|
||||
<p>"进程隐藏" 能让特定的进程对操作系统的常规检测机制变得不可见。在黑客攻击或系统防御的场景中,这种技术都可能被应用。具体来说,Linux 系统中每个进程都在 /proc/ 目录下有一个以其进程 ID 命名的子文件夹,包含了该进程的各种信息。<code>ps</code> 命令就是通过查找这些文件夹来显示进程信息的。因此,如果我们能隐藏某个进程的 /proc/ 文件夹,就能让这个进程对 <code>ps</code> 命令等检测手段“隐身”。</p>
|
||||
<p>要实现进程隐藏,关键在于操作 <code>/proc/</code> 目录。在 Linux 中,<code>getdents64</code> 系统调用可以读取目录下的文件信息。我们可以通过挂接这个系统调用,修改它返回的结果,从而达到隐藏文件的目的。实现这个功能需要使用到 eBPF 的 <code>bpf_probe_write_user</code> 功能,它可以修改用户空间的内存,因此能用来修改 <code>getdents64</code> 返回的结果。</p>
|
||||
<p>下面,我们会详细介绍如何在内核态和用户态编写 eBPF 程序来实现进程隐藏。</p>
|
||||
<h3 id="内核态-ebpf-程序实现"><a class="header" href="#内核态-ebpf-程序实现">内核态 eBPF 程序实现</a></h3>
|
||||
<p>接下来,我们将详细介绍如何在内核态编写 eBPF 程序来实现进程隐藏。首先是 eBPF 程序的起始部分:</p>
|
||||
<pre><code class="language-c">// SPDX-License-Identifier: BSD-3-Clause
|
||||
#include "vmlinux.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "common.h"
|
||||
#include "common.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
// Ringbuffer Map to pass messages from kernel to user
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
} rb SEC(".maps");
|
||||
|
||||
// Map to fold the dents buffer addresses
|
||||
struct {
|
||||
@@ -203,7 +203,7 @@ struct {
|
||||
__uint(max_entries, 8192);
|
||||
__type(key, size_t);
|
||||
__type(value, long unsigned int);
|
||||
} map_buffs SEC(".maps");
|
||||
} map_buffs SEC(".maps");
|
||||
|
||||
// Map used to enable searching through the
|
||||
// data in a loop
|
||||
@@ -212,7 +212,7 @@ struct {
|
||||
__uint(max_entries, 8192);
|
||||
__type(key, size_t);
|
||||
__type(value, int);
|
||||
} map_bytes_read SEC(".maps");
|
||||
} map_bytes_read SEC(".maps");
|
||||
|
||||
// Map with address of actual
|
||||
struct {
|
||||
@@ -220,7 +220,7 @@ struct {
|
||||
__uint(max_entries, 8192);
|
||||
__type(key, size_t);
|
||||
__type(value, long unsigned int);
|
||||
} map_to_patch SEC(".maps");
|
||||
} map_to_patch SEC(".maps");
|
||||
|
||||
// Map to hold program tail calls
|
||||
struct {
|
||||
@@ -228,23 +228,23 @@ struct {
|
||||
__uint(max_entries, 5);
|
||||
__type(key, __u32);
|
||||
__type(value, __u32);
|
||||
} map_prog_array SEC(".maps");
|
||||
} map_prog_array SEC(".maps");
|
||||
</code></pre>
|
||||
<p>我们首先需要理解这个 eBPF 程序的基本构成和使用到的几个重要组件。前几行引用了几个重要的头文件,如 "vmlinux.h"、"bpf_helpers.h"、"bpf_tracing.h" 和 "bpf_core_read.h"。这些文件提供了 eBPF 编程所需的基础设施和一些重要的函数或宏。</p>
|
||||
<p>我们首先需要理解这个 eBPF 程序的基本构成和使用到的几个重要组件。前几行引用了几个重要的头文件,如 "vmlinux.h"、"bpf_helpers.h"、"bpf_tracing.h" 和 "bpf_core_read.h"。这些文件提供了 eBPF 编程所需的基础设施和一些重要的函数或宏。</p>
|
||||
<ul>
|
||||
<li>"vmlinux.h" 是一个包含了完整的内核数据结构的头文件,是从 vmlinux 内核二进制中提取的。使用这个头文件,eBPF 程序可以访问内核的数据结构。</li>
|
||||
<li>"bpf_helpers.h" 头文件中定义了一系列的宏,这些宏是 eBPF 程序使用的 BPF 助手(helper)函数的封装。这些 BPF 助手函数是 eBPF 程序和内核交互的主要方式。</li>
|
||||
<li>"bpf_tracing.h" 是用于跟踪事件的头文件,它包含了许多宏和函数,这些都是为了简化 eBPF 程序对跟踪点(tracepoint)的操作。</li>
|
||||
<li>"bpf_core_read.h" 头文件提供了一组用于从内核读取数据的宏和函数。</li>
|
||||
<li>"vmlinux.h" 是一个包含了完整的内核数据结构的头文件,是从 vmlinux 内核二进制中提取的。使用这个头文件,eBPF 程序可以访问内核的数据结构。</li>
|
||||
<li>"bpf_helpers.h" 头文件中定义了一系列的宏,这些宏是 eBPF 程序使用的 BPF 助手(helper)函数的封装。这些 BPF 助手函数是 eBPF 程序和内核交互的主要方式。</li>
|
||||
<li>"bpf_tracing.h" 是用于跟踪事件的头文件,它包含了许多宏和函数,这些都是为了简化 eBPF 程序对跟踪点(tracepoint)的操作。</li>
|
||||
<li>"bpf_core_read.h" 头文件提供了一组用于从内核读取数据的宏和函数。</li>
|
||||
</ul>
|
||||
<p>程序中定义了一系列的 map 结构,这些 map 是 eBPF 程序中的主要数据结构,它们用于在内核态和用户态之间共享数据,或者在 eBPF 程序中存储和传递数据。</p>
|
||||
<p>其中,"rb" 是一个 Ringbuffer 类型的 map,它用于从内核向用户态传递消息。Ringbuffer 是一种能在内核和用户态之间高效传递大量数据的数据结构。</p>
|
||||
<p>"map_buffs" 是一个 Hash 类型的 map,它用于存储目录项(dentry)的缓冲区地址。</p>
|
||||
<p>"map_bytes_read" 是另一个 Hash 类型的 map,它用于在数据循环中启用搜索。</p>
|
||||
<p>"map_to_patch" 是另一个 Hash 类型的 map,存储了需要被修改的目录项(dentry)的地址。</p>
|
||||
<p>"map_prog_array" 是一个 Prog Array 类型的 map,它用于保存程序的尾部调用。</p>
|
||||
<p>程序中的 "target_ppid" 和 "pid_to_hide_len"、"pid_to_hide" 是几个重要的全局变量,它们分别存储了目标父进程的 PID、需要隐藏的 PID 的长度以及需要隐藏的 PID。</p>
|
||||
<p>接下来的代码部分,程序定义了一个名为 "linux_dirent64" 的结构体,这个结构体代表一个 Linux 目录项。然后程序定义了两个函数,"handle_getdents_enter" 和 "handle_getdents_exit",这两个函数分别在 getdents64 系统调用的入口和出口被调用,用于实现对目录项的操作。</p>
|
||||
<p>其中,"rb" 是一个 Ringbuffer 类型的 map,它用于从内核向用户态传递消息。Ringbuffer 是一种能在内核和用户态之间高效传递大量数据的数据结构。</p>
|
||||
<p>"map_buffs" 是一个 Hash 类型的 map,它用于存储目录项(dentry)的缓冲区地址。</p>
|
||||
<p>"map_bytes_read" 是另一个 Hash 类型的 map,它用于在数据循环中启用搜索。</p>
|
||||
<p>"map_to_patch" 是另一个 Hash 类型的 map,存储了需要被修改的目录项(dentry)的地址。</p>
|
||||
<p>"map_prog_array" 是一个 Prog Array 类型的 map,它用于保存程序的尾部调用。</p>
|
||||
<p>程序中的 "target_ppid" 和 "pid_to_hide_len"、"pid_to_hide" 是几个重要的全局变量,它们分别存储了目标父进程的 PID、需要隐藏的 PID 的长度以及需要隐藏的 PID。</p>
|
||||
<p>接下来的代码部分,程序定义了一个名为 "linux_dirent64" 的结构体,这个结构体代表一个 Linux 目录项。然后程序定义了两个函数,"handle_getdents_enter" 和 "handle_getdents_exit",这两个函数分别在 getdents64 系统调用的入口和出口被调用,用于实现对目录项的操作。</p>
|
||||
<pre><code class="language-c">
|
||||
// Optional Target Parent PID
|
||||
const volatile int target_ppid = 0;
|
||||
@@ -262,7 +262,7 @@ const volatile char pid_to_hide[max_pid_len];
|
||||
// unsigned char d_type; /* File type */
|
||||
// char d_name[]; /* Filename (null-terminated) */ };
|
||||
// int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
|
||||
SEC("tp/syscalls/sys_enter_getdents64")
|
||||
SEC("tp/syscalls/sys_enter_getdents64")
|
||||
int handle_getdents_enter(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
size_t pid_tgid = bpf_get_current_pid_tgid();
|
||||
@@ -296,7 +296,7 @@ int handle_getdents_enter(struct trace_event_raw_sys_enter *ctx)
|
||||
<p>在接下来的代码段中,我们将要实现在 <code>getdents64</code> 系统调用返回时的处理。我们主要的目标就是找到我们想要隐藏的进程,并且对目录项进行修改以实现隐藏。</p>
|
||||
<p>我们首先定义了一个名为 <code>handle_getdents_exit</code> 的函数,它将在 <code>getdents64</code> 系统调用返回时被调用。</p>
|
||||
<pre><code class="language-c">
|
||||
SEC("tp/syscalls/sys_exit_getdents64")
|
||||
SEC("tp/syscalls/sys_exit_getdents64")
|
||||
int handle_getdents_exit(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
size_t pid_tgid = bpf_get_current_pid_tgid();
|
||||
@@ -372,7 +372,7 @@ int handle_getdents_exit(struct trace_event_raw_sys_exit *ctx)
|
||||
<p>在这个函数中,我们首先获取了当前进程的 PID 和线程组 ID,然后检查系统调用是否读取到了目录的内容。如果没有读取到内容,我们就直接返回。</p>
|
||||
<p>然后我们从 <code>map_buffs</code> 这个 map 中获取 <code>getdents64</code> 系统调用入口处保存的目录内容的地址。如果我们没有保存过这个地址,那么就没有必要进行进一步的处理。</p>
|
||||
<p>接下来的部分有点复杂,我们用了一个循环来迭代读取目录的内容,并且检查是否有我们想要隐藏的进程的 PID。如果我们找到了,我们就用 <code>bpf_tail_call</code> 函数跳转到 <code>handle_getdents_patch</code> 函数,进行实际的隐藏操作。</p>
|
||||
<pre><code class="language-c">SEC("tp/syscalls/sys_exit_getdents64")
|
||||
<pre><code class="language-c">SEC("tp/syscalls/sys_exit_getdents64")
|
||||
int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
// Only patch if we've already checked and found our pid's folder to hide
|
||||
@@ -398,10 +398,10 @@ int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
|
||||
char filename[max_pid_len];
|
||||
bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp_previous->d_name);
|
||||
filename[pid_to_hide_len-1] = 0x00;
|
||||
bpf_printk("[PID_HIDE] filename previous %s\n", filename);
|
||||
bpf_printk("[PID_HIDE] filename previous %s\n", filename);
|
||||
bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name);
|
||||
filename[pid_to_hide_len-1] = 0x00;
|
||||
bpf_printk("[PID_HIDE] filename next one %s\n", filename);
|
||||
bpf_printk("[PID_HIDE] filename next one %s\n", filename);
|
||||
|
||||
// Attempt to overwrite
|
||||
short unsigned int d_reclen_new = d_reclen_previous + d_reclen;
|
||||
@@ -439,7 +439,7 @@ int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
|
||||
<pre><code class="language-c"> skel = pidhide_bpf__open();
|
||||
if (!skel)
|
||||
{
|
||||
fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno));
|
||||
fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -449,7 +449,7 @@ int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
env.pid_to_hide = getpid();
|
||||
}
|
||||
sprintf(pid_to_hide, "%d", env.pid_to_hide);
|
||||
sprintf(pid_to_hide, "%d", env.pid_to_hide);
|
||||
strncpy(skel->rodata->pid_to_hide, pid_to_hide, sizeof(skel->rodata->pid_to_hide));
|
||||
skel->rodata->pid_to_hide_len = strlen(pid_to_hide) + 1;
|
||||
skel->rodata->target_ppid = env.target_ppid;
|
||||
@@ -458,13 +458,13 @@ int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
|
||||
<pre><code class="language-c"> err = pidhide_bpf__load(skel);
|
||||
if (err)
|
||||
{
|
||||
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
|
||||
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
|
||||
goto cleanup;
|
||||
}
|
||||
</code></pre>
|
||||
<p>最后,我们等待并处理由 eBPF 程序发送的事件。这个过程是通过调用 <code>ring_buffer__poll</code> 函数实现的。在这个过程中,我们每隔一段时间就检查一次环形缓冲区中是否有新的事件。如果有,我们就调用 <code>handle_event</code> 函数来处理这个事件。</p>
|
||||
<pre><code class="language-c">printf("Successfully started!\n");
|
||||
printf("Hiding PID %d\n", env.pid_to_hide);
|
||||
<pre><code class="language-c">printf("Successfully started!\n");
|
||||
printf("Hiding PID %d\n", env.pid_to_hide);
|
||||
while (!exiting)
|
||||
{
|
||||
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
|
||||
@@ -476,7 +476,7 @@ while (!exiting)
|
||||
}
|
||||
if (err < 0)
|
||||
{
|
||||
printf("Error polling perf buffer: %d\n", err);
|
||||
printf("Error polling perf buffer: %d\n", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -487,9 +487,9 @@ while (!exiting)
|
||||
{
|
||||
const struct event *e = data;
|
||||
if (e->success)
|
||||
printf("Hid PID from program %d (%s)\n", e->pid, e->comm);
|
||||
printf("Hid PID from program %d (%s)\n", e->pid, e->comm);
|
||||
else
|
||||
printf("Failed to hide PID from program %d (%s)\n", e->pid, e->comm);
|
||||
printf("Failed to hide PID from program %d (%s)\n", e->pid, e->comm);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -219,24 +219,24 @@ struct event {
|
||||
<h3 id="2-ebpf-程序-signalbpfc"><a class="header" href="#2-ebpf-程序-signalbpfc">2. eBPF 程序 (<code>signal.bpf.c</code>)</a></h3>
|
||||
<p>signal.bpf.c</p>
|
||||
<pre><code class="language-c">// SPDX-License-Identifier: BSD-3-Clause
|
||||
#include "vmlinux.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "common.h"
|
||||
#include "common.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
// Ringbuffer Map to pass messages from kernel to user
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
} rb SEC(".maps");
|
||||
|
||||
// Optional Target Parent PID
|
||||
const volatile int target_ppid = 0;
|
||||
|
||||
SEC("tp/syscalls/sys_enter_ptrace")
|
||||
SEC("tp/syscalls/sys_enter_ptrace")
|
||||
int bpf_dos(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
long ret = 0;
|
||||
@@ -271,7 +271,7 @@ int bpf_dos(struct trace_event_raw_sys_enter *ctx)
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>许可证声明</strong></p>
|
||||
<p>声明了程序的许可证为 "Dual BSD/GPL",这是为了满足 Linux 内核对 eBPF 程序的许可要求。</p>
|
||||
<p>声明了程序的许可证为 "Dual BSD/GPL",这是为了满足 Linux 内核对 eBPF 程序的许可要求。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Ringbuffer Map</strong></p>
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="使用-ebpf-替换任意程序读取或写入的文本"><a class="header" href="#使用-ebpf-替换任意程序读取或写入的文本">使用 eBPF 替换任意程序读取或写入的文本</a></h1>
|
||||
<p>完整源代码:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/27-replace">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/27-replace</a> </p>
|
||||
<p>完整源代码:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/27-replace">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/27-replace</a></p>
|
||||
<p>关于如何安装依赖,请参考:<a href="https://eunomia.dev/tutorials/11-bootstrap/">https://eunomia.dev/tutorials/11-bootstrap/</a></p>
|
||||
<p>编译:</p>
|
||||
<pre><code class="language-bash">make
|
||||
|
||||
@@ -175,8 +175,8 @@
|
||||
<main>
|
||||
<h1 id="在应用程序退出后运行-ebpf-程序ebpf-程序的生命周期"><a class="header" href="#在应用程序退出后运行-ebpf-程序ebpf-程序的生命周期">在应用程序退出后运行 eBPF 程序:eBPF 程序的生命周期</a></h1>
|
||||
<p>eBPF(Extended Berkeley Packet Filter)是 Linux 内核中的一项重大技术创新,允许用户在内核空间中执行自定义程序,而无需修改内核源代码或加载任何内核模块。这为开发人员提供了极大的灵活性,可以观察、修改和控制 Linux 系统。</p>
|
||||
<p>本文将介绍 eBPF 程序的生命周期,以及如何在用户空间应用程序退出后继续运行 eBPF 程序的方法,还将介绍如何使用 "pin" 在不同进程之间共享 eBPF 对象。本文是 eBPF 开发者教程的一部分,更多详细信息可以在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 和 <a href="https://eunomia.dev/tutorials">https://eunomia.dev/tutorials</a> 中找到。</p>
|
||||
<p>通过使用 "detach" 方法来运行 eBPF 程序,用户空间加载程序可以在不停止 eBPF 程序的情况下退出。另外,使用 "pin" 的方法可以在进程之间共享 eBPF 对象,使其保持活动状态。</p>
|
||||
<p>本文将介绍 eBPF 程序的生命周期,以及如何在用户空间应用程序退出后继续运行 eBPF 程序的方法,还将介绍如何使用 "pin" 在不同进程之间共享 eBPF 对象。本文是 eBPF 开发者教程的一部分,更多详细信息可以在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 和 <a href="https://eunomia.dev/tutorials">https://eunomia.dev/tutorials</a> 中找到。</p>
|
||||
<p>通过使用 "detach" 方法来运行 eBPF 程序,用户空间加载程序可以在不停止 eBPF 程序的情况下退出。另外,使用 "pin" 的方法可以在进程之间共享 eBPF 对象,使其保持活动状态。</p>
|
||||
<h2 id="ebpf-程序的生命周期"><a class="header" href="#ebpf-程序的生命周期">eBPF 程序的生命周期</a></h2>
|
||||
<p>BPF对象(包括程序、映射和调试信息)通过文件描述符(FD)进行访问,并具有引用计数器。每个对象都有一个引用计数器,用于追踪对象被引用的次数。例如,当创建一个映射时,内核会分配一个struct bpf_map对象,并将其引用计数器初始化为1。然后,将映射的文件描述符返回给用户空间进程。如果进程退出或崩溃,文件描述符将被关闭,并且映射的引用计数将减少。当引用计数为零时,内存将被释放。</p>
|
||||
<p>BPF程序使用 maps 有两个阶段。首先,创建 maps 并将其文件描述符存储为BPF_LD_IMM64指令的一部分。当内核验证程序时,它会增加程序使用的 maps 的引用计数,并将程序的引用计数初始化为1。此时,用户空间可以关闭与maps 相关的文件描述符,但 maps 不会被销毁,因为程序仍然在使用它们。当程序文件描述符关闭且引用计数为零时,销毁逻辑将减少 maps 的引用计数。这允许多个不同类型的程序同时使用同一个 maps。</p>
|
||||
@@ -199,7 +199,7 @@
|
||||
int err;
|
||||
err = bpf_program__pin(prog, path);
|
||||
if (err) {
|
||||
fprintf(stdout, "could not pin prog %s: %d\n", path, err);
|
||||
fprintf(stdout, "could not pin prog %s: %d\n", path, err);
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
@@ -210,7 +210,7 @@ int pin_map(struct bpf_map *map, const char* path)
|
||||
int err;
|
||||
err = bpf_map__pin(map, path);
|
||||
if (err) {
|
||||
fprintf(stdout, "could not pin map %s: %d\n", path, err);
|
||||
fprintf(stdout, "could not pin map %s: %d\n", path, err);
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
@@ -221,7 +221,7 @@ int pin_link(struct bpf_link *link, const char* path)
|
||||
int err;
|
||||
err = bpf_link__pin(link, path);
|
||||
if (err) {
|
||||
fprintf(stdout, "could not pin link %s: %d\n", path, err);
|
||||
fprintf(stdout, "could not pin link %s: %d\n", path, err);
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
<h2 id="示例程序"><a class="header" href="#示例程序">示例程序</a></h2>
|
||||
<p>此示例程序从发送者的套接字(出口)重定向流量至接收者的套接字(入口),<strong>跳过 TCP/IP 内核网络栈</strong>。在这个示例中,我们假定发送者和接收者都在<strong>同一台</strong>机器上运行。这个示例程序有两个部分,它们共享一个 map 定义:</p>
|
||||
<p>bpf_sockmap.h</p>
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
@@ -205,7 +205,7 @@ struct {
|
||||
__uint(max_entries, 65535);
|
||||
__type(key, struct sock_key);
|
||||
__type(value, int);
|
||||
} sock_ops_map SEC(".maps");
|
||||
} sock_ops_map SEC(".maps");
|
||||
</code></pre>
|
||||
<p>这个示例程序中的 BPF 程序被分为两个部分 <code>bpf_redirect.bpf.c</code> 和 <code>bpf_contrack.bpf.c</code>。</p>
|
||||
<ul>
|
||||
@@ -227,11 +227,11 @@ struct {
|
||||
</ol>
|
||||
<p>这个示例程序就是通过 BPF 实现了在本地通信时,快速将消息从发送者的套接字重定向到接收者的套接字,从而绕过了内核网络栈,以提高传输效率。</p>
|
||||
<p>bpf_redirect.bpf.c</p>
|
||||
<pre><code class="language-c">#include "bpf_sockmap.h"
|
||||
<pre><code class="language-c">#include "bpf_sockmap.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("sk_msg")
|
||||
SEC("sk_msg")
|
||||
int bpf_redir(struct sk_msg_md *msg)
|
||||
{
|
||||
if(msg->remote_ip4 != LOCALHOST_IPV4 || msg->local_ip4!= LOCALHOST_IPV4)
|
||||
@@ -248,11 +248,11 @@ int bpf_redir(struct sk_msg_md *msg)
|
||||
}
|
||||
</code></pre>
|
||||
<p>bpf_contrack.bpf.c</p>
|
||||
<pre><code class="language-c">#include "bpf_sockmap.h"
|
||||
<pre><code class="language-c">#include "bpf_sockmap.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("sockops")
|
||||
SEC("sockops")
|
||||
int bpf_sockops_handler(struct bpf_sock_ops *skops){
|
||||
u32 family, op;
|
||||
|
||||
@@ -275,7 +275,7 @@ int bpf_sockops_handler(struct bpf_sock_ops *skops){
|
||||
.family = skops->family,
|
||||
};
|
||||
|
||||
bpf_printk(">>> new connection: OP:%d, PORT:%d --> %d\n", op, bpf_ntohl(key.sport), bpf_ntohl(key.dport));
|
||||
bpf_printk(">>> new connection: OP:%d, PORT:%d --> %d\n", op, bpf_ntohl(key.sport), bpf_ntohl(key.dport));
|
||||
|
||||
bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
|
||||
return BPF_OK;
|
||||
@@ -298,28 +298,28 @@ set -e
|
||||
sudo mount -t bpf bpf /sys/fs/bpf/
|
||||
|
||||
# check if old program already loaded
|
||||
if [ -e "/sys/fs/bpf/bpf_sockops" ]; then
|
||||
echo ">>> bpf_sockops already loaded, uninstalling..."
|
||||
if [ -e "/sys/fs/bpf/bpf_sockops" ]; then
|
||||
echo ">>> bpf_sockops already loaded, uninstalling..."
|
||||
./unload.sh
|
||||
echo ">>> old program already deleted..."
|
||||
echo ">>> old program already deleted..."
|
||||
fi
|
||||
|
||||
# load and attach sock_ops program
|
||||
sudo bpftool prog load bpf_contrack.bpf.o /sys/fs/bpf/bpf_sockops type sockops pinmaps /sys/fs/bpf/
|
||||
sudo bpftool cgroup attach "/sys/fs/cgroup/" sock_ops pinned "/sys/fs/bpf/bpf_sockops"
|
||||
sudo bpftool cgroup attach "/sys/fs/cgroup/" sock_ops pinned "/sys/fs/bpf/bpf_sockops"
|
||||
|
||||
# load and attach sk_msg program
|
||||
sudo bpftool prog load bpf_redirect.bpf.o "/sys/fs/bpf/bpf_redir" map name sock_ops_map pinned "/sys/fs/bpf/sock_ops_map"
|
||||
sudo bpftool prog load bpf_redirect.bpf.o "/sys/fs/bpf/bpf_redir" map name sock_ops_map pinned "/sys/fs/bpf/sock_ops_map"
|
||||
sudo bpftool prog attach pinned /sys/fs/bpf/bpf_redir msg_verdict pinned /sys/fs/bpf/sock_ops_map
|
||||
</code></pre>
|
||||
<p>这是一个 BPF 的加载脚本。它的主要功能是加载和附加 BPF 程序到内核系统中,并将关联的 BPF map 一并存储(pin)到 BPF 文件系统中,以便 BPF 程序能访问和操作这些 map。</p>
|
||||
<p>让我们详细地看一下脚本的每一行是做什么的。</p>
|
||||
<ol>
|
||||
<li><code>sudo mount -t bpf bpf /sys/fs/bpf/</code> 这一行用于挂载 BPF 文件系统,使得 BPF 程序和相关的 map 可以被系统访问和操作。</li>
|
||||
<li>判断条件 <code>[ -e "/sys/fs/bpf/bpf_sockops" ]</code> 是检查是否已经存在 <code>/sys/fs/bpf/bpf_sockops</code> 文件,如果存在,则说明 <code>bpf_sockops</code> 程序已经被加载到系统中,那么将会通过 <code>./unload.sh</code> 脚本将其卸载。</li>
|
||||
<li>判断条件 <code>[ -e "/sys/fs/bpf/bpf_sockops" ]</code> 是检查是否已经存在 <code>/sys/fs/bpf/bpf_sockops</code> 文件,如果存在,则说明 <code>bpf_sockops</code> 程序已经被加载到系统中,那么将会通过 <code>./unload.sh</code> 脚本将其卸载。</li>
|
||||
<li><code>sudo bpftool prog load bpf_contrack.bpf.o /sys/fs/bpf/bpf_sockops type sockops pinmaps /sys/fs/bpf/</code> 这一行是加载上文中 <code>bpf_contrack.bpf.c</code> 编译得到的 BPF 对象文件 <code>bpf_contrack.bpf.o</code> 到 BPF 文件系统中,存储至 <code>/sys/fs/bpf/bpf_sockops</code>,并且指定它的类型为 <code>sockops</code>。<code>pinmaps /sys/fs/bpf/</code> 是指定将加载的 BPF 程序相关的 map 存储在 <code>/sys/fs/bpf/</code> 下。</li>
|
||||
<li><code>sudo bpftool cgroup attach "/sys/fs/cgroup/" sock_ops pinned "/sys/fs/bpf/bpf_sockops"</code> 这一行是将已经加载到 BPF 文件系统的 <code>bpf_sockops</code> 程序附加到 cgroup(此路径为"/sys/fs/cgroup/")。附加后,所有属于这个 cgroup 的套接字操作都会受到 <code>bpf_sockops</code> 的影响。</li>
|
||||
<li><code>sudo bpftool prog load bpf_redirect.bpf.o "/sys/fs/bpf/bpf_redir" map name sock_ops_map pinned "/sys/fs/bpf/sock_ops_map"</code> 这一行是加载 <code>bpf_redirect.bpf.c</code> 编译得到的 BPF 对象文件 <code>bpf_redirect.bpf.o</code> 到 BPF 文件系统中,存储至 <code>/sys/fs/bpf/bpf_redir</code> ,并且指定它的相关 map为 <code>sock_ops_map</code>,这个map在 <code>/sys/fs/bpf/sock_ops_map</code> 中。</li>
|
||||
<li><code>sudo bpftool cgroup attach "/sys/fs/cgroup/" sock_ops pinned "/sys/fs/bpf/bpf_sockops"</code> 这一行是将已经加载到 BPF 文件系统的 <code>bpf_sockops</code> 程序附加到 cgroup(此路径为"/sys/fs/cgroup/")。附加后,所有属于这个 cgroup 的套接字操作都会受到 <code>bpf_sockops</code> 的影响。</li>
|
||||
<li><code>sudo bpftool prog load bpf_redirect.bpf.o "/sys/fs/bpf/bpf_redir" map name sock_ops_map pinned "/sys/fs/bpf/sock_ops_map"</code> 这一行是加载 <code>bpf_redirect.bpf.c</code> 编译得到的 BPF 对象文件 <code>bpf_redirect.bpf.o</code> 到 BPF 文件系统中,存储至 <code>/sys/fs/bpf/bpf_redir</code> ,并且指定它的相关 map为 <code>sock_ops_map</code>,这个map在 <code>/sys/fs/bpf/sock_ops_map</code> 中。</li>
|
||||
<li><code>sudo bpftool prog attach pinned /sys/fs/bpf/bpf_redir msg_verdict pinned /sys/fs/bpf/sock_ops_map</code> 这一行是将已经加载的 <code>bpf_redir</code> 附加到 <code>sock_ops_map</code> 上,附加方式为 <code>msg_verdict</code>,表示当该 map 对应的套接字收到消息时,将会调用 <code>bpf_redir</code> 程序处理。</li>
|
||||
</ol>
|
||||
<p>综上,此脚本的主要作用就是将两个用于处理本地套接字流量的 BPF 程序分别加载到系统并附加到正确的位置,以便它们能被正确地调用,并且确保它们可以访问和操作相关的 BPF map。</p>
|
||||
|
||||
@@ -179,29 +179,29 @@
|
||||
<h2 id="fentry"><a class="header" href="#fentry">Fentry</a></h2>
|
||||
<p>fentry(function entry)和 fexit(function exit)是 eBPF(扩展的伯克利包过滤器)中的两种探针类型,用于在 Linux 内核函数的入口和退出处进行跟踪。它们允许开发者在内核函数执行的特定阶段收集信息、修改参数或观察返回值。这种跟踪和监控功能在性能分析、故障排查和安全分析等场景中非常有用。</p>
|
||||
<p>与 kprobes 相比,fentry 和 fexit 程序有更高的性能和可用性。在这个例子中,我们可以直接访问函数的指针参数,就像在普通的 C 代码中一样,而不需要使用各种读取帮助程序。fexit 和 kretprobe 程序最大的区别在于,fexit 程序可以访问函数的输入参数和返回值,而 kretprobe 只能访问返回值。从 5.5 内核开始,fentry 和 fexit 对 eBPF 程序可用。</p>
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("fentry/do_unlinkat")
|
||||
SEC("fentry/do_unlinkat")
|
||||
int BPF_PROG(do_unlinkat, int dfd, struct filename *name)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name);
|
||||
bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("fexit/do_unlinkat")
|
||||
SEC("fexit/do_unlinkat")
|
||||
int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret);
|
||||
bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -321,12 +321,12 @@ struct probe_SSL_data_t {
|
||||
<li>最后,将数据发送到用户空间。</li>
|
||||
</ol>
|
||||
<p>注意:我们使用了两个用户返回探针 <code>uretprobe</code> 来分别 hook <code>SSL_read</code> 和 <code>SSL_write</code> 的返回:</p>
|
||||
<pre><code class="language-c">SEC("uretprobe/SSL_read")
|
||||
<pre><code class="language-c">SEC("uretprobe/SSL_read")
|
||||
int BPF_URETPROBE(probe_SSL_read_exit) {
|
||||
return (SSL_exit(ctx, 0)); // 0 表示读操作
|
||||
}
|
||||
|
||||
SEC("uretprobe/SSL_write")
|
||||
SEC("uretprobe/SSL_write")
|
||||
int BPF_URETPROBE(probe_SSL_write_exit) {
|
||||
return (SSL_exit(ctx, 1)); // 1 表示写操作
|
||||
}
|
||||
@@ -336,7 +336,7 @@ int BPF_URETPROBE(probe_SSL_write_exit) {
|
||||
<h4 id="进入握手"><a class="header" href="#进入握手">进入握手</a></h4>
|
||||
<p>我们使用 <code>uprobe</code> 为 <code>do_handshake</code> 设置一个 probe:</p>
|
||||
<pre><code class="language-c">
|
||||
SEC("uprobe/do_handshake")
|
||||
SEC("uprobe/do_handshake")
|
||||
int BPF_UPROBE(probe_SSL_do_handshake_enter, void *ssl) {
|
||||
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
u32 pid = pid_tgid >> 32;
|
||||
@@ -362,7 +362,7 @@ int BPF_UPROBE(probe_SSL_do_handshake_enter, void *ssl) {
|
||||
<h4 id="退出握手"><a class="header" href="#退出握手">退出握手</a></h4>
|
||||
<p>同样,我们为 <code>do_handshake</code> 的返回设置了一个 <code>uretprobe</code>:</p>
|
||||
<pre><code class="language-c">
|
||||
SEC("uretprobe/do_handshake")
|
||||
SEC("uretprobe/do_handshake")
|
||||
int BPF_URETPROBE(probe_SSL_do_handshake_exit) {
|
||||
u32 zero = 0;
|
||||
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
@@ -427,18 +427,18 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) {
|
||||
<p>上述代码片段中,根据环境变量 <code>env</code> 的设定,程序可以选择针对三种常见的加密库(OpenSSL、GnuTLS 和 NSS)进行挂载。这意味着我们可以在同一个工具中对多种库的调用进行追踪。</p>
|
||||
<p>为了实现这一功能,首先利用 <code>find_library_path</code> 函数确定库的路径。然后,根据库的类型,调用对应的 <code>attach_</code> 函数来将 eBPF 程序挂载到库函数上。</p>
|
||||
<pre><code class="language-c"> if (env.openssl) {
|
||||
char *openssl_path = find_library_path("libssl.so");
|
||||
printf("OpenSSL path: %s\n", openssl_path);
|
||||
attach_openssl(obj, "/lib/x86_64-linux-gnu/libssl.so.3");
|
||||
char *openssl_path = find_library_path("libssl.so");
|
||||
printf("OpenSSL path: %s\n", openssl_path);
|
||||
attach_openssl(obj, "/lib/x86_64-linux-gnu/libssl.so.3");
|
||||
}
|
||||
if (env.gnutls) {
|
||||
char *gnutls_path = find_library_path("libgnutls.so");
|
||||
printf("GnuTLS path: %s\n", gnutls_path);
|
||||
char *gnutls_path = find_library_path("libgnutls.so");
|
||||
printf("GnuTLS path: %s\n", gnutls_path);
|
||||
attach_gnutls(obj, gnutls_path);
|
||||
}
|
||||
if (env.nss) {
|
||||
char *nss_path = find_library_path("libnspr4.so");
|
||||
printf("NSS path: %s\n", nss_path);
|
||||
char *nss_path = find_library_path("libnspr4.so");
|
||||
printf("NSS path: %s\n", nss_path);
|
||||
attach_nss(obj, nss_path);
|
||||
}
|
||||
</code></pre>
|
||||
@@ -498,7 +498,7 @@ int attach_nss(struct sslsniff_bpf *skel, const char *lib) {
|
||||
<pre><code class="language-c"> while (!exiting) {
|
||||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
warn("error polling perf buffer: %s\n", strerror(-err));
|
||||
warn("error polling perf buffer: %s\n", strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
err = 0;
|
||||
@@ -514,13 +514,13 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) {
|
||||
char hex_data[MAX_BUF_SIZE * 2 + 1] = {0};
|
||||
buf_to_hex((uint8_t *)buf, buf_size, hex_data);
|
||||
|
||||
printf("\n%s\n", s_mark);
|
||||
printf("\n%s\n", s_mark);
|
||||
for (size_t i = 0; i < strlen(hex_data); i += 32) {
|
||||
printf("%.32s\n", hex_data + i);
|
||||
printf("%.32s\n", hex_data + i);
|
||||
}
|
||||
printf("%s\n\n", e_mark);
|
||||
printf("%s\n\n", e_mark);
|
||||
} else {
|
||||
printf("\n%s\n%s\n%s\n\n", s_mark, buf, e_mark);
|
||||
printf("\n%s\n%s\n%s\n\n", s_mark, buf, e_mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
OUTPUT := .output
|
||||
CLANG ?= clang
|
||||
LIBBPF_SRC := $(abspath ../third_party/libbpf/src)
|
||||
BPFTOOL_SRC := $(abspath ../third_party/bpftool/src)
|
||||
LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a)
|
||||
BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool)
|
||||
BPFTOOL ?= $(BPFTOOL_OUTPUT)/goroutine/bpftool
|
||||
LIBBLAZESYM_SRC := $(abspath ../third_party/blazesym/)
|
||||
LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a)
|
||||
LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h)
|
||||
ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \
|
||||
| sed 's/arm.*/arm/' \
|
||||
| sed 's/aarch64/arm64/' \
|
||||
| sed 's/ppc64le/powerpc/' \
|
||||
| sed 's/mips.*/mips/' \
|
||||
| sed 's/riscv64/riscv/' \
|
||||
| sed 's/loongarch64/loongarch/')
|
||||
VMLINUX := ../third_party/vmlinux/$(ARCH)/vmlinux.h
|
||||
# Use our own libbpf API headers and Linux UAPI headers distributed with
|
||||
# libbpf to avoid dependency on system-wide headers, which could be missing or
|
||||
# outdated
|
||||
INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX))
|
||||
CFLAGS := -g -Wall
|
||||
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)
|
||||
|
||||
APPS = goroutine # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall
|
||||
|
||||
CARGO ?= $(shell which cargo)
|
||||
ifeq ($(strip $(CARGO)),)
|
||||
BZS_APPS :=
|
||||
else
|
||||
BZS_APPS := # profile
|
||||
APPS += $(BZS_APPS)
|
||||
# Required by libblazesym
|
||||
ALL_LDFLAGS += -lrt -ldl -lpthread -lm
|
||||
endif
|
||||
|
||||
# Get Clang's default includes on this system. We'll explicitly add these dirs
|
||||
# to the includes list when compiling with `-target bpf` because otherwise some
|
||||
# architecture-specific dirs will be "missing" on some architectures/distros -
|
||||
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
|
||||
# sys/cdefs.h etc. might be missing.
|
||||
#
|
||||
# Use '-idirafter': Don't interfere with include mechanics except where the
|
||||
# build would have failed anyways.
|
||||
CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - </dev/null 2>&1 \
|
||||
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
|
||||
|
||||
ifeq ($(V),1)
|
||||
Q =
|
||||
msg =
|
||||
else
|
||||
Q = @
|
||||
msg = @printf ' %-8s %s%s\n' \
|
||||
"$(1)" \
|
||||
"$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \
|
||||
"$(if $(3), $(3))";
|
||||
MAKEFLAGS += --no-print-directory
|
||||
endif
|
||||
|
||||
define allow-override
|
||||
$(if $(or $(findstring environment,$(origin $(1))),\
|
||||
$(findstring command line,$(origin $(1)))),,\
|
||||
$(eval $(1) = $(2)))
|
||||
endef
|
||||
|
||||
$(call allow-override,CC,$(CROSS_COMPILE)cc)
|
||||
$(call allow-override,LD,$(CROSS_COMPILE)ld)
|
||||
|
||||
.PHONY: all
|
||||
all: $(APPS)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(call msg,CLEAN)
|
||||
$(Q)rm -rf $(OUTPUT) $(APPS)
|
||||
|
||||
$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
|
||||
$(call msg,MKDIR,$@)
|
||||
$(Q)mkdir -p $@
|
||||
|
||||
# Build libbpf
|
||||
$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
|
||||
$(call msg,LIB,$@)
|
||||
$(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \
|
||||
OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \
|
||||
INCLUDEDIR= LIBDIR= UAPIDIR= \
|
||||
install
|
||||
|
||||
# Build bpftool
|
||||
$(BPFTOOL): | $(BPFTOOL_OUTPUT)
|
||||
$(call msg,BPFTOOL,$@)
|
||||
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) goroutine
|
||||
|
||||
|
||||
$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
|
||||
$(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release
|
||||
|
||||
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
|
||||
$(call msg,LIB, $@)
|
||||
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
|
||||
|
||||
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
|
||||
$(call msg,LIB,$@)
|
||||
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
|
||||
|
||||
# Build BPF code
|
||||
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
|
||||
$(call msg,BPF,$@)
|
||||
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
|
||||
$(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \
|
||||
-c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
$(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
|
||||
# Generate BPF skeletons
|
||||
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL)
|
||||
$(call msg,GEN-SKEL,$@)
|
||||
$(Q)$(BPFTOOL) gen skeleton $< > $@
|
||||
|
||||
# Build user-space code
|
||||
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
|
||||
|
||||
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
|
||||
$(call msg,CC,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
|
||||
|
||||
$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
|
||||
|
||||
$(BZS_APPS): $(LIBBLAZESYM_OBJ)
|
||||
|
||||
# Build application binary
|
||||
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
|
||||
$(call msg,BINARY,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@
|
||||
|
||||
# delete failed targets
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
# keep intermediate (.skel.h, .bpf.o, etc) targets
|
||||
.SECONDARY:
|
||||
BIN
31-goroutine/go-server-http/main
Executable file
BIN
31-goroutine/go-server-http/main
Executable file
Binary file not shown.
34
31-goroutine/go-server-http/main.go
Normal file
34
31-goroutine/go-server-http/main.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var http_body []byte
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(http_body)
|
||||
}
|
||||
|
||||
func main() {
|
||||
args := os.Args
|
||||
if len(args) > 1 {
|
||||
body_len, _ := strconv.ParseInt(args[1], 10, 64)
|
||||
http_body = make([]byte, body_len)
|
||||
rand.Read(http_body)
|
||||
fmt.Println("Body set to", body_len, "bytes of random stuff")
|
||||
} else {
|
||||
http_body = []byte("Hello,World!")
|
||||
}
|
||||
http.HandleFunc("/", handler)
|
||||
fmt.Println("Server started!")
|
||||
err := http.ListenAndServe(":447", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This code runs using bpf in the Linux kernel.
|
||||
* Copyright 2022- The Yunshan Networks Authors.
|
||||
*
|
||||
*
|
||||
* Modify from https://github.com/deepflowio/deepflow
|
||||
* By Yusheng Zheng <1067852565@qq.com>
|
||||
*
|
||||
@@ -17,359 +17,44 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*/
|
||||
#include "vmlinux.h"
|
||||
#include <vmlinux.h>
|
||||
#include "goroutine.h"
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
#define NAME(N) __##N
|
||||
|
||||
#define HASH_ENTRIES_MAX 40960
|
||||
#define MAX_SYSTEM_THREADS 40960
|
||||
|
||||
struct sched_comm_fork_ctx {
|
||||
__u64 __pad_0;
|
||||
char parent_comm[16];
|
||||
__u32 parent_pid;
|
||||
char child_comm[16];
|
||||
__u32 child_pid;
|
||||
};
|
||||
#define GOID_OFFSET 0x98
|
||||
|
||||
struct sched_comm_exit_ctx {
|
||||
__u64 __pad_0; /* 0 8 */
|
||||
char comm[16]; /* offset:8; size:16 */
|
||||
pid_t pid; /* offset:24; size:4 */
|
||||
int prio; /* offset:28; size:4 */
|
||||
};
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
|
||||
// struct ebpf_proc_info -> offsets[] arrays index.
|
||||
enum offsets_index {
|
||||
OFFSET_IDX_GOID_RUNTIME_G,
|
||||
OFFSET_IDX_CONN_TLS_CONN,
|
||||
OFFSET_IDX_SYSFD_POLL_FD,
|
||||
OFFSET_IDX_CONN_HTTP2_SERVER_CONN,
|
||||
OFFSET_IDX_TCONN_HTTP2_CLIENT_CONN,
|
||||
OFFSET_IDX_CC_HTTP2_CLIENT_CONN_READ_LOOP,
|
||||
OFFSET_IDX_CONN_GRPC_HTTP2_CLIENT,
|
||||
OFFSET_IDX_CONN_GRPC_HTTP2_SERVER,
|
||||
OFFSET_IDX_FRAMER_GRPC_TRANSPORT_LOOPY_WRITER,
|
||||
OFFSET_IDX_WRITER_GRPC_TRANSPORT_FRAMER,
|
||||
OFFSET_IDX_CONN_GRPC_TRANSPORT_BUFWRITER,
|
||||
OFFSET_IDX_SIDE_GRPC_TRANSPORT_LOOPY_WRITER,
|
||||
OFFSET_IDX_FIELDS_HTTP2_META_HEADERS_FRAME,
|
||||
OFFSET_IDX_STREAM_HTTP2_CLIENT_CONN,
|
||||
OFFSET_IDX_STREAM_ID_HTTP2_FRAME_HEADER,
|
||||
OFFSET_IDX_MAX,
|
||||
};
|
||||
|
||||
// Store the ebpf_proc_info to eBPF Map.
|
||||
struct ebpf_proc_info {
|
||||
__u32 version;
|
||||
__u16 offsets[OFFSET_IDX_MAX];
|
||||
|
||||
// In golang, itab represents type, and in interface, struct is represented
|
||||
// by the address of itab. We use itab to judge the structure type, and
|
||||
// find the fd representing the connection after multiple jumps. These
|
||||
// types are not available in Go ELF files without a symbol table.
|
||||
// Go 用 itab 表示类型, 在 interface 中通过 itab 确定具体的 struct, 并根据
|
||||
// struct 找到表示连接的 fd.
|
||||
__u64 net_TCPConn_itab;
|
||||
__u64 crypto_tls_Conn_itab; // TLS_HTTP1,TLS_HTTP2
|
||||
__u64 credentials_syscallConn_itab; // gRPC
|
||||
};
|
||||
|
||||
#define GO_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
|
||||
|
||||
// Go implements a new way of passing function arguments and results using
|
||||
// registers instead of the stack. We need the go version and the computer
|
||||
// architecture to determine the parameter locations
|
||||
static __inline bool is_register_based_call(struct ebpf_proc_info *info)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
// https://go.dev/doc/go1.17
|
||||
return info->version >= GO_VERSION(1, 17, 0);
|
||||
#elif defined(__aarch64__)
|
||||
// https://groups.google.com/g/golang-checkins/c/SO9OmZYkOXU
|
||||
return info->version >= GO_VERSION(1, 18, 0);
|
||||
#else
|
||||
_Pragma("error \"Must specify a BPF target arch\"");
|
||||
#endif
|
||||
SEC("uprobe/./go-server-http/main:runtime.casgstatus")
|
||||
int uprobe_runtime_casgstatus(struct pt_regs *ctx) {
|
||||
int newval = ctx->cx;
|
||||
void *gp = ctx->ax;
|
||||
struct goroutine_execute_data *data;
|
||||
u64 goid;
|
||||
if (bpf_probe_read_user(&goid, sizeof(goid), gp + GOID_OFFSET) == 0) {
|
||||
data = bpf_ringbuf_reserve(&rb, sizeof(*data), 0);
|
||||
if (data) {
|
||||
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
data->pid = pid_tgid;
|
||||
data->tgid = pid_tgid >> 32;
|
||||
data->goid = goid;
|
||||
data->state = newval;
|
||||
bpf_ringbuf_submit(data, 0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct bpf_map_def {
|
||||
unsigned int type;
|
||||
unsigned int key_size;
|
||||
unsigned int value_size;
|
||||
unsigned int max_entries;
|
||||
};
|
||||
|
||||
// Process ID and coroutine ID, marking the coroutine in the system
|
||||
struct go_key {
|
||||
__u32 tgid;
|
||||
__u64 goid;
|
||||
} __attribute__((packed));
|
||||
|
||||
// The mapping of coroutines to ancestors, the map is updated when a new
|
||||
// coroutine is created
|
||||
// key : current gorouting (struct go_key)
|
||||
// value : ancerstor goid
|
||||
struct bpf_map_def SEC("maps") go_ancerstor_map = {
|
||||
.type = BPF_MAP_TYPE_LRU_HASH,
|
||||
.key_size = sizeof(struct go_key),
|
||||
.value_size = sizeof(__u64),
|
||||
.max_entries = HASH_ENTRIES_MAX,
|
||||
};
|
||||
|
||||
// Used to determine the timeout, as a termination condition for finding
|
||||
// ancestors.
|
||||
// key : current gorouting (struct go_key)
|
||||
// value: timestamp when the data was inserted into the map
|
||||
struct bpf_map_def SEC("maps") go_rw_ts_map = {
|
||||
.type = BPF_MAP_TYPE_LRU_HASH,
|
||||
.key_size = sizeof(struct go_key),
|
||||
.value_size = sizeof(__u64),
|
||||
.max_entries = HASH_ENTRIES_MAX,
|
||||
};
|
||||
|
||||
/*
|
||||
* The binary executable file offset of the GO process
|
||||
* key: pid
|
||||
* value: struct ebpf_proc_info
|
||||
*/
|
||||
struct bpf_map_def SEC("maps") proc_info_map = {
|
||||
.type = BPF_MAP_TYPE_HASH,
|
||||
.key_size = sizeof(int),
|
||||
.value_size = sizeof(struct ebpf_proc_info),
|
||||
.max_entries = HASH_ENTRIES_MAX,
|
||||
};
|
||||
|
||||
// Pass data between coroutine entry and exit functions
|
||||
struct go_newproc_caller {
|
||||
__u64 goid;
|
||||
void *sp; // stack pointer
|
||||
} __attribute__((packed));
|
||||
|
||||
struct bpf_map_def SEC("maps") pid_tgid_callerid_map = {
|
||||
.type = BPF_MAP_TYPE_HASH,
|
||||
.key_size = sizeof(__u64),
|
||||
.value_size = sizeof(struct go_newproc_caller),
|
||||
.max_entries = HASH_ENTRIES_MAX,
|
||||
};
|
||||
|
||||
/*
|
||||
* Goroutines Map
|
||||
* key: {tgid, pid}
|
||||
* value: goroutine ID
|
||||
*/
|
||||
struct bpf_map_def SEC("maps") goroutines_map = {
|
||||
.type = BPF_MAP_TYPE_HASH,
|
||||
.key_size = sizeof(__u64),
|
||||
.value_size = sizeof(__u64),
|
||||
.max_entries = MAX_SYSTEM_THREADS,
|
||||
};
|
||||
|
||||
SEC("uprobe/runtime.execute")
|
||||
int runtime_execute(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u32 tgid = pid_tgid >> 32;
|
||||
|
||||
struct ebpf_proc_info *info = bpf_map_lookup_elem(&proc_info_map, &tgid);
|
||||
if (!info) {
|
||||
return 0;
|
||||
}
|
||||
int offset_g_goid = info->offsets[OFFSET_IDX_GOID_RUNTIME_G];
|
||||
if (offset_g_goid < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *g_ptr;
|
||||
|
||||
if (is_register_based_call(info)) {
|
||||
g_ptr = (void *)PT_GO_REGS_PARM1(ctx);
|
||||
} else {
|
||||
bpf_probe_read(&g_ptr, sizeof(g_ptr), (void *)(PT_REGS_SP(ctx) + 8));
|
||||
}
|
||||
|
||||
__s64 goid = 0;
|
||||
bpf_probe_read(&goid, sizeof(goid), g_ptr + offset_g_goid);
|
||||
bpf_map_update_elem(&goroutines_map, &pid_tgid, &goid, BPF_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This function creates a new go coroutine, and the parent and child
|
||||
// coroutine numbers are in the parameters and return values respectively.
|
||||
// Pass the function parameters through pid_tgid_callerid_map
|
||||
//
|
||||
// go 1.15 ~ 1.17: func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) *g
|
||||
// go1.18+ :func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g
|
||||
SEC("uprobe/enter_runtime.newproc1")
|
||||
int enter_runtime_newproc1(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u32 tgid = pid_tgid >> 32;
|
||||
|
||||
struct ebpf_proc_info *info =
|
||||
bpf_map_lookup_elem(&proc_info_map, &tgid);
|
||||
if (!info) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// go less than 1.15 cannot get parent-child coroutine relationship
|
||||
// ~ go1.14: func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr)
|
||||
if (info->version < GO_VERSION(1, 15, 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int offset_g_goid = info->offsets[OFFSET_IDX_GOID_RUNTIME_G];
|
||||
if (offset_g_goid < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *g_ptr;
|
||||
if (is_register_based_call(info)) {
|
||||
// https://github.com/golang/go/commit/8e5304f7298a0eef48e4796017c51b4d9aeb52b5
|
||||
if (info->version >= GO_VERSION(1, 18, 0)) {
|
||||
g_ptr = (void *)PT_GO_REGS_PARM2(ctx);
|
||||
} else {
|
||||
g_ptr = (void *)PT_GO_REGS_PARM4(ctx);
|
||||
}
|
||||
} else {
|
||||
if (info->version >= GO_VERSION(1, 18, 0)) {
|
||||
bpf_probe_read(&g_ptr, sizeof(g_ptr),
|
||||
(void *)(PT_REGS_SP(ctx) + 16));
|
||||
} else {
|
||||
bpf_probe_read(&g_ptr, sizeof(g_ptr),
|
||||
(void *)(PT_REGS_SP(ctx) + 32));
|
||||
}
|
||||
}
|
||||
|
||||
__s64 goid = 0;
|
||||
bpf_probe_read(&goid, sizeof(goid), g_ptr + offset_g_goid);
|
||||
if (!goid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct go_newproc_caller caller = {
|
||||
.goid = goid,
|
||||
.sp = (void *)PT_REGS_SP(ctx),
|
||||
};
|
||||
bpf_map_update_elem(&pid_tgid_callerid_map, &pid_tgid, &caller,
|
||||
BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The mapping relationship between parent and child coroutines is stored in go_ancerstor_map
|
||||
//
|
||||
// go 1.15 ~ 1.17: func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) *g
|
||||
// go1.18+ :func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g
|
||||
SEC("uprobe/exit_runtime.newproc1")
|
||||
int exit_runtime_newproc1(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u32 tgid = pid_tgid >> 32;
|
||||
|
||||
struct ebpf_proc_info *info =
|
||||
bpf_map_lookup_elem(&proc_info_map, &tgid);
|
||||
if (!info) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(info->version < GO_VERSION(1, 15, 0)){
|
||||
return 0;
|
||||
}
|
||||
|
||||
int offset_g_goid = info->offsets[OFFSET_IDX_GOID_RUNTIME_G];
|
||||
if (offset_g_goid < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct go_newproc_caller *caller =
|
||||
bpf_map_lookup_elem(&pid_tgid_callerid_map, &pid_tgid);
|
||||
if (!caller) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *g_ptr;
|
||||
if (is_register_based_call(info)) {
|
||||
g_ptr = (void *)PT_GO_REGS_PARM1(ctx);
|
||||
} else {
|
||||
if (info->version >= GO_VERSION(1, 18, 0)) {
|
||||
bpf_probe_read(&g_ptr, sizeof(g_ptr), caller->sp + 32);
|
||||
} else {
|
||||
bpf_probe_read(&g_ptr, sizeof(g_ptr), caller->sp + 48);
|
||||
}
|
||||
}
|
||||
|
||||
__s64 goid = 0;
|
||||
bpf_probe_read(&goid, sizeof(goid), g_ptr + offset_g_goid);
|
||||
if (!goid) {
|
||||
bpf_map_delete_elem(&pid_tgid_callerid_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct go_key key = { .tgid = tgid, .goid = goid };
|
||||
goid = caller->goid;
|
||||
bpf_map_update_elem(&go_ancerstor_map, &key, &goid, BPF_ANY);
|
||||
|
||||
bpf_map_delete_elem(&pid_tgid_callerid_map, &pid_tgid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// /sys/kernel/debug/tracing/events/sched/sched_process_exit/format
|
||||
SEC("tracepoint/sched/sched_process_exit")
|
||||
int bpf_func_sched_process_exit(struct sched_comm_exit_ctx *ctx)
|
||||
{
|
||||
pid_t pid, tid;
|
||||
__u64 id;
|
||||
|
||||
id = bpf_get_current_pid_tgid();
|
||||
pid = id >> 32;
|
||||
tid = (__u32)id;
|
||||
|
||||
// If is a process, clear proc_info_map element and submit event.
|
||||
if (pid == tid) {
|
||||
bpf_map_delete_elem(&proc_info_map, &pid);
|
||||
struct process_event_t data;
|
||||
data.pid = pid;
|
||||
data.meta.event_type = EVENT_TYPE_PROC_EXIT;
|
||||
bpf_get_current_comm(data.name, sizeof(data.name));
|
||||
// int ret = bpf_perf_event_output(ctx, &NAME(socket_data),
|
||||
// BPF_F_CURRENT_CPU, &data,
|
||||
// sizeof(data));
|
||||
|
||||
// if (ret) {
|
||||
// bpf_debug
|
||||
// ("bpf_func_sched_process_exit event output failed: %d\n",
|
||||
// ret);
|
||||
// }
|
||||
}
|
||||
|
||||
bpf_map_delete_elem(&goroutines_map, &id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// /sys/kernel/debug/tracing/events/sched/sched_process_fork/format
|
||||
SEC("tracepoint/sched/sched_process_fork")
|
||||
int bpf_func_sched_process_fork(struct sched_comm_fork_ctx *ctx)
|
||||
{
|
||||
struct process_event_t data;
|
||||
|
||||
data.meta.event_type = EVENT_TYPE_PROC_EXEC;
|
||||
data.pid = ctx->child_pid;
|
||||
bpf_get_current_comm(data.name, sizeof(data.name));
|
||||
// int ret = bpf_perf_event_output(ctx, &NAME(socket_data),
|
||||
// BPF_F_CURRENT_CPU, &data, sizeof(data));
|
||||
|
||||
// if (ret) {
|
||||
// bpf_debug(
|
||||
// "bpf_func_sys_exit_execve event output() failed: %d\n",
|
||||
// ret);
|
||||
// }
|
||||
return 0;
|
||||
}
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
#ifndef EBPF_EXAMPLE_GOROUTINE_H
|
||||
#define EBPF_EXAMPLE_GOROUTINE_H
|
||||
|
||||
|
||||
enum {
|
||||
/*
|
||||
* 0 ~ 16 for L7 socket event (struct socket_data_buffer),
|
||||
* indicates the number of socket data in socket_data_buffer.
|
||||
*/
|
||||
|
||||
/*
|
||||
* For event registrion
|
||||
*/
|
||||
EVENT_TYPE_MIN = 1 << 5,
|
||||
EVENT_TYPE_PROC_EXEC = 1 << 5,
|
||||
EVENT_TYPE_PROC_EXIT = 1 << 6
|
||||
// Add new event type here.
|
||||
enum goroutine_state {
|
||||
IDLE,
|
||||
RUNNABLE,
|
||||
RUNNING,
|
||||
SYSCALL,
|
||||
WAITING,
|
||||
MORIBUND_UNUSED,
|
||||
DEAD,
|
||||
ENQUEUE_UNUSED,
|
||||
COPYSTACK,
|
||||
PREEMPTED,
|
||||
};
|
||||
|
||||
// Description Provides basic information about an event
|
||||
struct event_meta {
|
||||
__u32 event_type;
|
||||
struct goroutine_execute_data {
|
||||
enum goroutine_state state;
|
||||
unsigned long goid;
|
||||
int pid;
|
||||
int tgid;
|
||||
};
|
||||
|
||||
// Process execution or exit event data
|
||||
struct process_event_t {
|
||||
struct event_meta meta;
|
||||
__u32 pid; // process ID
|
||||
__u8 name[TASK_COMM_LEN]; // process name
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
</ol>
|
||||
<p>如果该技术被恶意软件利用,攻击者可以重定向文件操作,导致数据泄漏或者破坏数据完整性。例如,程序写入日志文件时,攻击者可能将数据重定向到控制的文件中,干扰审计跟踪。</p>
|
||||
<p>内核态代码(部分,完整内容请参考 Github bpf-developer-tutorial):</p>
|
||||
<pre><code class="language-c">SEC("tracepoint/syscalls/sys_enter_openat")
|
||||
<pre><code class="language-c">SEC("tracepoint/syscalls/sys_enter_openat")
|
||||
int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
u64 pid = bpf_get_current_pid_tgid() >> 32;
|
||||
@@ -202,7 +202,7 @@ int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter *ctx
|
||||
args.fname = (const char *)ctx->args[1];
|
||||
args.flags = (int)ctx->args[2];
|
||||
if (rewrite) {
|
||||
bpf_probe_write_user((char*)ctx->args[1], "hijacked", 9);
|
||||
bpf_probe_write_user((char*)ctx->args[1], "hijacked", 9);
|
||||
}
|
||||
bpf_map_update_elem(&start, &pid, &args, 0);
|
||||
return 0;
|
||||
@@ -213,7 +213,7 @@ int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter *ctx
|
||||
<li><code>bpf_get_current_pid_tgid()</code> 获取当前进程ID。</li>
|
||||
<li>如果指定了 <code>target_pid</code> 并且不匹配当前进程ID,函数直接返回。</li>
|
||||
<li>我们创建一个 <code>args_t</code> 结构来存储文件名和标志。</li>
|
||||
<li>使用 <code>bpf_probe_write_user</code> 修改用户空间内存中的文件名为 "hijacked"。</li>
|
||||
<li>使用 <code>bpf_probe_write_user</code> 修改用户空间内存中的文件名为 "hijacked"。</li>
|
||||
</ul>
|
||||
<p>eunomia-bpf 是一个开源的 eBPF 动态加载运行时和开发工具链,它的目的是简化 eBPF 程序的开发、构建、分发、运行。可以参考 <a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a> 或 <a href="https://eunomia.dev/tutorials/1-helloworld/">https://eunomia.dev/tutorials/1-helloworld/</a> 下载和安装 ecc 编译工具链和 ecli 运行时。我们使用 eunomia-bpf 编译运行这个例子。</p>
|
||||
<p>编译:</p>
|
||||
@@ -222,27 +222,27 @@ int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter *ctx
|
||||
<p>使用 make 构建一个简单的 victim 程序,用来测试:</p>
|
||||
<pre><code class="language-c">int main()
|
||||
{
|
||||
char filename[100] = "my_test.txt";
|
||||
char filename[100] = "my_test.txt";
|
||||
// print pid
|
||||
int pid = getpid();
|
||||
std::cout << "current pid: " << pid << std::endl;
|
||||
system("echo \"hello\" > my_test.txt");
|
||||
system("echo \"world\" >> hijacked");
|
||||
std::cout << "current pid: " << pid << std::endl;
|
||||
system("echo \"hello\" > my_test.txt");
|
||||
system("echo \"world\" >> hijacked");
|
||||
while (true) {
|
||||
std::cout << "Opening my_test.txt" << std::endl;
|
||||
std::cout << "Opening my_test.txt" << std::endl;
|
||||
|
||||
int fd = open(filename, O_RDONLY);
|
||||
assert(fd != -1);
|
||||
|
||||
std::cout << "test.txt opened, fd=" << fd << std::endl;
|
||||
std::cout << "test.txt opened, fd=" << fd << std::endl;
|
||||
usleep(1000 * 300);
|
||||
// print the file content
|
||||
char buf[100] = {0};
|
||||
int ret = read(fd, buf, 5);
|
||||
std::cout << "read " << ret << " bytes: " << buf << std::endl;
|
||||
std::cout << "Closing test.txt..." << std::endl;
|
||||
std::cout << "read " << ret << " bytes: " << buf << std::endl;
|
||||
std::cout << "Closing test.txt..." << std::endl;
|
||||
close(fd);
|
||||
std::cout << "test.txt closed" << std::endl;
|
||||
std::cout << "test.txt closed" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -257,7 +257,7 @@ test.txt closed
|
||||
<p>可以使用以下命令指定应修改其 <code>openat</code> 系统调用参数的目标进程ID:</p>
|
||||
<pre><code class="language-bash">sudo ./ecli run package.json --rewrite --target_pid=$(pidof victim)
|
||||
</code></pre>
|
||||
<p>然后就会发现输出变成了 world,可以看到我们原先想要打开 "my_test.txt" 文件,但是实际上被劫持打开了 hijacked 文件:</p>
|
||||
<p>然后就会发现输出变成了 world,可以看到我们原先想要打开 "my_test.txt" 文件,但是实际上被劫持打开了 hijacked 文件:</p>
|
||||
<pre><code class="language-console">test.txt opened, fd=3
|
||||
read 5 bytes: hello
|
||||
Closing test.txt...
|
||||
@@ -274,7 +274,7 @@ read 5 bytes: world
|
||||
<p>包含测试用例的完整代码可以在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 找到。</p>
|
||||
<h2 id="修改-bash-execve-的进程名称"><a class="header" href="#修改-bash-execve-的进程名称">修改 bash execve 的进程名称</a></h2>
|
||||
<p>这段功能用于当 <code>execve</code> 系统调用进行时修改执行程序名称。在一些审计或监控场景,这可能用于记录特定进程的行为或修改其行为。然而,此类篡改可能会造成混淆,使得用户或管理员难以确定系统实际执行的程序是什么。最严重的风险是,如果恶意用户能够控制 eBPF 程序,他们可以将合法的系统命令重定向到恶意软件,造成严重的安全威胁。</p>
|
||||
<pre><code class="language-c">SEC("tp/syscalls/sys_enter_execve")
|
||||
<pre><code class="language-c">SEC("tp/syscalls/sys_enter_execve")
|
||||
int handle_execve_enter(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
size_t pid_tgid = bpf_get_current_pid_tgid();
|
||||
@@ -294,11 +294,11 @@ int handle_execve_enter(struct trace_event_raw_sys_enter *ctx)
|
||||
bpf_probe_read_user(&prog_name, TASK_COMM_LEN, (void*)ctx->args[0]);
|
||||
bpf_probe_read_user(&prog_name_orig, TASK_COMM_LEN, (void*)ctx->args[0]);
|
||||
prog_name[TASK_COMM_LEN-1] = '\x00';
|
||||
bpf_printk("[EXECVE_HIJACK] %s\n", prog_name);
|
||||
bpf_printk("[EXECVE_HIJACK] %s\n", prog_name);
|
||||
|
||||
// Program can't be less than out two-char name
|
||||
if (prog_name[1] == '\x00') {
|
||||
bpf_printk("[EXECVE_HIJACK] program name too small\n");
|
||||
bpf_printk("[EXECVE_HIJACK] program name too small\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
</ul>
|
||||
<p>下面,我们将通过一段代码示例,详细展示如何利用 user ring buffer,实现从用户态向内核传送数据,并以 kernel ring buffer 相应地从内核态向用户态传送数据。</p>
|
||||
<h2 id="一实现在用户态和内核态间使用-ring-buffer-传送数据"><a class="header" href="#一实现在用户态和内核态间使用-ring-buffer-传送数据">一、实现:在用户态和内核态间使用 ring buffer 传送数据</a></h2>
|
||||
<p>借助新的 BPF MAP,我们可以实现在用户态和内核态间通过环形缓冲区传送数据。在这个示例中,我们将详细说明如何在用户空间创建一个 "用户环形缓冲区" (user ring buffer) 并向其写入数据,然后在内核空间中通过 <code>bpf_user_ringbuf_drain</code> 函数来消费这些数据。同时,我们也会使用 "内核环形缓冲区" (kernel ring buffer) 来从内核空间反馈数据到用户空间。为此,我们需要在用户空间和内核空间分别创建并操作这两个环形缓冲区。</p>
|
||||
<p>借助新的 BPF MAP,我们可以实现在用户态和内核态间通过环形缓冲区传送数据。在这个示例中,我们将详细说明如何在用户空间创建一个 "用户环形缓冲区" (user ring buffer) 并向其写入数据,然后在内核空间中通过 <code>bpf_user_ringbuf_drain</code> 函数来消费这些数据。同时,我们也会使用 "内核环形缓冲区" (kernel ring buffer) 来从内核空间反馈数据到用户空间。为此,我们需要在用户空间和内核空间分别创建并操作这两个环形缓冲区。</p>
|
||||
<p>完整的代码可以在 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/35-user-ringbuf">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/35-user-ringbuf</a> 中找到。</p>
|
||||
<h3 id="创建环形缓冲区"><a class="header" href="#创建环形缓冲区">创建环形缓冲区</a></h3>
|
||||
<p>在内核空间,我们创建了一个类型为 <code>BPF_MAP_TYPE_USER_RINGBUF</code> 的 <code>user_ringbuf</code>,以及一个类型为 <code>BPF_MAP_TYPE_RINGBUF</code> 的 <code>kernel_ringbuf</code>。在用户空间,我们创建了一个 <code>struct ring_buffer_user</code> 结构体的实例,并通过 <code>ring_buffer_user__new</code> 函数和对应的操作来管理这个用户环形缓冲区。</p>
|
||||
@@ -201,7 +201,7 @@
|
||||
if (!rb)
|
||||
{
|
||||
err = -1;
|
||||
fprintf(stderr, "Failed to create ring buffer\n");
|
||||
fprintf(stderr, "Failed to create ring buffer\n");
|
||||
goto cleanup;
|
||||
}
|
||||
user_ringbuf = user_ring_buffer__new(bpf_map__fd(skel->maps.user_ringbuf), NULL);
|
||||
@@ -211,25 +211,25 @@
|
||||
<pre><code class="language-c">// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "user_ringbuf.h"
|
||||
#include "user_ringbuf.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} user_ringbuf SEC(".maps");
|
||||
} user_ringbuf SEC(".maps");
|
||||
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} kernel_ringbuf SEC(".maps");
|
||||
} kernel_ringbuf SEC(".maps");
|
||||
|
||||
int read = 0;
|
||||
|
||||
@@ -255,7 +255,7 @@ do_nothing_cb(struct bpf_dynptr *dynptr, void *context)
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tracepoint/syscalls/sys_exit_kill")
|
||||
SEC("tracepoint/syscalls/sys_exit_kill")
|
||||
int kill_exit(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
long num_samples;
|
||||
@@ -282,9 +282,9 @@ int kill_exit(struct trace_event_raw_sys_exit *ctx)
|
||||
}
|
||||
|
||||
entry->i = getpid();
|
||||
strcpy(entry->comm, "hello");
|
||||
strcpy(entry->comm, "hello");
|
||||
|
||||
int read = snprintf(entry->comm, sizeof(entry->comm), "%u", i);
|
||||
int read = snprintf(entry->comm, sizeof(entry->comm), "%u", i);
|
||||
if (read <= 0)
|
||||
{
|
||||
/* Assert on the error path to avoid spamming logs with
|
||||
@@ -308,8 +308,8 @@ done:
|
||||
<pre><code class="language-c"> write_samples(user_ringbuf);
|
||||
|
||||
/* Process events */
|
||||
printf("%-8s %-5s %-16s %-7s %-7s %s\n",
|
||||
"TIME", "EVENT", "COMM", "PID", "PPID", "FILENAME/EXIT CODE");
|
||||
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 */);
|
||||
@@ -321,7 +321,7 @@ done:
|
||||
}
|
||||
if (err < 0)
|
||||
{
|
||||
printf("Error polling perf buffer: %d\n", err);
|
||||
printf("Error polling perf buffer: %d\n", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
<p>本文旨在对用户空间的 eBPF 运行时和对应的一些应用场景进行剖析和总结。尽管大多数人对基于内核的 eBPF 已有所了解,用户空间 eBPF 的进展和应用实践同样引人注目。本文还将探讨用户空间 eBPF 运行时与 Wasm 运行时的技术比较,后者在云原生和边缘计算领域已获得广泛的关注。我们也新开源了一个用户态 eBPF 运行时 <a href="https://github.com/eunomia-bpf/bpftime">bpftime</a>。通过 LLVM <code>JIT/AOT</code> 后端支持,我们的基准测试表明 bpftime 是最快的用户空间 eBPF 运行时之一,同时还可以让内核中间的 eBPF Uprobe 无缝在用户空间运行,获得近十倍的性能提升。</p>
|
||||
<h2 id="ebpf内核的动态扩展运行时与字节码"><a class="header" href="#ebpf内核的动态扩展运行时与字节码">eBPF:内核的动态扩展运行时与字节码</a></h2>
|
||||
<h3 id="ebpf-究竟是何方神圣"><a class="header" href="#ebpf-究竟是何方神圣">eBPF 究竟是何方神圣?</a></h3>
|
||||
<p>eBPF,全称 "extended Berkeley Packet Filter",是一项允许在不更改内核源代码或重启系统的情况下动态干预和修改内核行为的革命性技术。虽然 eBPF 起初是作为网络数据包过滤工具而设计,但如今已广泛应用于从性能分析到安全策略等多个方面,逐渐成为系统管理员的得力助手。</p>
|
||||
<p>eBPF,全称 "extended Berkeley Packet Filter",是一项允许在不更改内核源代码或重启系统的情况下动态干预和修改内核行为的革命性技术。虽然 eBPF 起初是作为网络数据包过滤工具而设计,但如今已广泛应用于从性能分析到安全策略等多个方面,逐渐成为系统管理员的得力助手。</p>
|
||||
<p>eBPF 的前身,Berkeley Packet Filter (BPF) —— 20 世纪 90 年代初的产物,主要用于网络数据包的高效过滤。尽管 BPF 已被广大用户所认可,eBPF 的出现则为其带来了更为广泛的指令集,并能直接与内核数据结构互动。自 2014 年 Linux 内核引入 eBPF 以后,它的影响力迅速扩张。Linux 的核心开发团队不断地完善 eBPF,使其从一个基础的网络数据包过滤器逐渐演变为一个功能强大的字节码引擎。</p>
|
||||
<h3 id="ebpf-对现代计算和网络的深远影响"><a class="header" href="#ebpf-对现代计算和网络的深远影响">eBPF 对现代计算和网络的深远影响</a></h3>
|
||||
<p>随着现代计算环境日益复杂,实时数据的采集和深入分析显得尤为重要。在这一背景下,eBPF 凭借其卓越的动态性,为开发者和管理员提供了实时干预系统行为的强大工具。eBPF 以其卓越的灵活性在现代网络解决方案中占据核心地位。它为流量控制、负载均衡及安全策略在内核级别提供了细致的控制手段,确保了系统的性能优化和安全稳定。同时,eBPF 在系统可观察性上也做出了显著贡献,为各种系统调用和硬件事件提供了详细的可编程追踪方案,促进了问题的迅速定位和解决。</p>
|
||||
@@ -291,7 +291,7 @@ continue malloc...
|
||||
</code></pre>
|
||||
<p>然后附加到该进程:</p>
|
||||
<pre><code class="language-console">$ sudo bpftime attach 101771 # 您可能需要以root身份运行make install
|
||||
Inject: "/root/.bpftime/libbpftime-agent.so"
|
||||
Inject: "/root/.bpftime/libbpftime-agent.so"
|
||||
成功注入。ID: 1
|
||||
</code></pre>
|
||||
<p>您可以看到原始程序的输出:</p>
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
<h2 id="最简单的例子symbol-name-mangling"><a class="header" href="#最简单的例子symbol-name-mangling">最简单的例子:Symbol name mangling</a></h2>
|
||||
<p>我们先来看一个简单的例子,使用 Uprobe 追踪 Rust 程序的 <code>main</code> 函数,代码如下:</p>
|
||||
<pre><pre class="playground"><code class="language-rust">pub fn hello() -> i32 {
|
||||
println!("Hello, world!");
|
||||
println!("Hello, world!");
|
||||
0
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ $ rustfilt -i name.txt | grep hello
|
||||
0000000000008b60 t helloworld::main
|
||||
</code></pre>
|
||||
<p>接下来我们可以尝试使用 bpftrace 跟踪对应的函数:</p>
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uprobe:helloworld/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }'
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uprobe:helloworld/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }'
|
||||
Attaching 1 probe...
|
||||
Function hello-world called
|
||||
</code></pre>
|
||||
@@ -223,7 +223,7 @@ Function hello-world called
|
||||
<pre><pre class="playground"><code class="language-rust">use std::env;
|
||||
|
||||
pub fn hello(i: i32, len: usize) -> i32 {
|
||||
println!("Hello, world! {} in {}", i, len);
|
||||
println!("Hello, world! {} in {}", i, len);
|
||||
i + len as i32
|
||||
}
|
||||
|
||||
@@ -235,16 +235,16 @@ fn main() {
|
||||
match arg.parse::<i32>() {
|
||||
Ok(i) => {
|
||||
let ret = hello(i, args.len());
|
||||
println!("return value: {}", ret);
|
||||
println!("return value: {}", ret);
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("Error: Argument '{}' is not a valid integer", arg);
|
||||
eprintln!("Error: Argument '{}' is not a valid integer", arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}</code></pre></pre>
|
||||
<p>我们再次进行类似的操作,会发现一个奇怪的现象:</p>
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }'
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called\n"); }'
|
||||
Attaching 1 probe...
|
||||
Function hello-world called
|
||||
</code></pre>
|
||||
@@ -260,7 +260,7 @@ Hello, world! 4 in 5
|
||||
return value: 9
|
||||
</code></pre>
|
||||
<p>而且看起来 bpftrace 并不能正确获取参数:</p>
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called %d\n"
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uprobe:args/target/release/helloworld:_ZN10helloworld4main17h2dce92cb81426b91E { printf("Function hello-world called %d\n"
|
||||
, arg0); }'
|
||||
Attaching 1 probe...
|
||||
Function hello-world called 63642464
|
||||
@@ -268,8 +268,8 @@ Function hello-world called 63642464
|
||||
<p>Uretprobe 捕捉到了第一次调用的返回值:</p>
|
||||
<pre><code class="language-console">$ sudo bpftrace -e 'uretprobe:args/tar
|
||||
get/release/helloworld:_ZN10helloworld4main17h2dce92
|
||||
cb81426b91E { printf("Function hello-world called %d
|
||||
\n", retval); }'
|
||||
cb81426b91E { printf("Function hello-world called %d
|
||||
\n", retval); }'
|
||||
Attaching 1 probe...
|
||||
Function hello-world called 6
|
||||
</code></pre>
|
||||
|
||||
@@ -214,17 +214,17 @@ struct data {
|
||||
int d;
|
||||
};
|
||||
|
||||
SEC("uprobe/examples/btf-base:add_test")
|
||||
SEC("uprobe/examples/btf-base:add_test")
|
||||
int BPF_UPROBE(add_test, struct data *d)
|
||||
{
|
||||
int a = 0, c = 0;
|
||||
bpf_probe_read_user(&a, sizeof(a), &d->a);
|
||||
bpf_probe_read_user(&c, sizeof(c), &d->c);
|
||||
bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c);
|
||||
bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c);
|
||||
return a + c;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
</code></pre>
|
||||
<p>然后,我们有两个不同版本的用户空间程序,<code>examples/btf-base</code>和<code>examples/btf-base-new</code>。两个版本中的struct <code>data</code>是不同的。</p>
|
||||
<p><code>examples/btf-base</code>:</p>
|
||||
@@ -241,7 +241,7 @@ int add_test(struct data *d) {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct data d = {1, 3, 4};
|
||||
printf("add_test(&d) = %d\n", add_test(&d));
|
||||
printf("add_test(&d) = %d\n", add_test(&d));
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -259,7 +259,7 @@ int add_test(struct data *d) {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct data d = {1, 2, 3, 4};
|
||||
printf("add_test(&d) = %d\n", add_test(&d));
|
||||
printf("add_test(&d) = %d\n", add_test(&d));
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
@@ -311,17 +311,17 @@ struct data {
|
||||
#pragma clang attribute pop
|
||||
#endif
|
||||
|
||||
SEC("uprobe/examples/btf-base:add_test")
|
||||
SEC("uprobe/examples/btf-base:add_test")
|
||||
int BPF_UPROBE(add_test, struct data *d)
|
||||
{
|
||||
int a = 0, c = 0;
|
||||
bpf_probe_read_user(&a, sizeof(a), &d->a);
|
||||
bpf_probe_read_user(&c, sizeof(c), &d->c);
|
||||
bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c);
|
||||
bpf_printk("add_test(&d) %d + %d = %d\n", a, c, a + c);
|
||||
return a + c;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
</code></pre>
|
||||
<p><code>struct data</code>的记录在eBPF程序中被保留下来。然后,我们可以使用 <code>btf-base.btf</code>来编译eBPF程序。</p>
|
||||
<p>将用户btf与内核btf合并,这样我们就有了一个完整的内核和用户空间的btf:</p>
|
||||
@@ -370,7 +370,7 @@ Successfully started! Press Ctrl+C to stop.
|
||||
);
|
||||
LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
|
||||
if (argc != 3 && argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <example-name> [<external-btf>]\n", argv[0]);
|
||||
fprintf(stderr, "Usage: %s <example-name> [<external-btf>]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (argc == 3)
|
||||
@@ -386,7 +386,7 @@ Successfully started! Press Ctrl+C to stop.
|
||||
/* Load and verify BPF application */
|
||||
skel = uprobe_bpf__open_opts(&opts);
|
||||
if (!skel) {
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
fprintf(stderr, "Failed to open and load BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
@@ -183,10 +183,10 @@
|
||||
<pre><code class="language-c">#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
/// @description "Process ID to trace"
|
||||
/// @description "Process ID to trace"
|
||||
const volatile int pid_target = 0;
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_openat")
|
||||
SEC("tracepoint/syscalls/sys_enter_openat")
|
||||
int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter* ctx)
|
||||
{
|
||||
u64 id = bpf_get_current_pid_tgid();
|
||||
@@ -195,23 +195,23 @@ int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter* ctx
|
||||
if (pid_target && pid_target != pid)
|
||||
return false;
|
||||
// Use bpf_printk to print the process information
|
||||
bpf_printk("Process ID: %d enter sys openat\n", pid);
|
||||
bpf_printk("Process ID: %d enter sys openat\n", pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// "Trace open family syscalls."
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
/// "Trace open family syscalls."
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这段 eBPF 程序实现了:</p>
|
||||
<ol>
|
||||
<li>引入头文件:<vmlinux.h> 包含了内核数据结构的定义,<bpf/bpf_helpers.h> 包含了 eBPF 程序所需的辅助函数。</li>
|
||||
<li>定义全局变量 <code>pid_target</code>,用于过滤指定进程 ID。这里设为 0 表示捕获所有进程的 sys_openat 调用。</li>
|
||||
<li>使用 <code>SEC</code> 宏定义一个 eBPF 程序,关联到 tracepoint "tracepoint/syscalls/sys_enter_openat"。这个 tracepoint 会在进程发起 <code>sys_openat</code> 系统调用时触发。</li>
|
||||
<li>使用 <code>SEC</code> 宏定义一个 eBPF 程序,关联到 tracepoint "tracepoint/syscalls/sys_enter_openat"。这个 tracepoint 会在进程发起 <code>sys_openat</code> 系统调用时触发。</li>
|
||||
<li>实现 eBPF 程序 <code>tracepoint__syscalls__sys_enter_openat</code>,它接收一个类型为 <code>struct trace_event_raw_sys_enter</code> 的参数 <code>ctx</code>。这个结构体包含了关于系统调用的信息。</li>
|
||||
<li>使用 <code>bpf_get_current_pid_tgid()</code> 函数获取当前进程的 PID 和 TID(线程 ID)。由于我们只关心 PID,所以将其值右移 32 位赋值给 <code>u32</code> 类型的变量 <code>pid</code>。</li>
|
||||
<li>检查 <code>pid_target</code> 变量是否与当前进程的 pid 相等。如果 <code>pid_target</code> 不为 0 且与当前进程的 pid 不相等,则返回 <code>false</code>,不对该进程的 <code>sys_openat</code> 调用进行捕获。</li>
|
||||
<li>使用 <code>bpf_printk()</code> 函数打印捕获到的进程 ID 和 <code>sys_openat</code> 调用的相关信息。这些信息可以在用户空间通过 BPF 工具查看。</li>
|
||||
<li>将程序许可证设置为 "GPL",这是运行 eBPF 程序的必要条件。</li>
|
||||
<li>将程序许可证设置为 "GPL",这是运行 eBPF 程序的必要条件。</li>
|
||||
</ol>
|
||||
<p>这个 eBPF 程序可以通过 libbpf 或 eunomia-bpf 等工具加载到内核并执行。它将捕获指定进程(或所有进程)的 sys_openat 系统调用,并在用户空间输出相关信息。</p>
|
||||
<p>eunomia-bpf 是一个结合 Wasm 的开源 eBPF 动态加载运行时和开发工具链,它的目的是简化 eBPF 程序的开发、构建、分发、运行。可以参考 <a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a> 下载和安装 ecc 编译工具链和 ecli 运行时。我们使用 eunomia-bpf 编译运行这个例子。完整代码请查看 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/4-opensnoop">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/4-opensnoop</a> 。</p>
|
||||
|
||||
@@ -197,11 +197,11 @@
|
||||
* binary can be an absolute/relative path or a filename; the latter is resolved to a
|
||||
* full binary path via bpf_program__attach_uprobe_opts.
|
||||
*
|
||||
* Specifying uprobe+ ensures we carry out strict matching; either "uprobe" must be
|
||||
* Specifying uprobe+ ensures we carry out strict matching; either "uprobe" must be
|
||||
* specified (and auto-attach is not possible) or the above format is specified for
|
||||
* auto-attach.
|
||||
*/
|
||||
SEC("uretprobe//bin/bash:readline")
|
||||
SEC("uretprobe//bin/bash:readline")
|
||||
int BPF_KRETPROBE(printret, const void *ret)
|
||||
{
|
||||
char str[MAX_LINE_SIZE];
|
||||
@@ -216,18 +216,18 @@ int BPF_KRETPROBE(printret, const void *ret)
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_probe_read_user_str(str, sizeof(str), ret);
|
||||
|
||||
bpf_printk("PID %d (%s) read: %s ", pid, comm, str);
|
||||
bpf_printk("PID %d (%s) read: %s ", pid, comm, str);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这段代码的作用是在 bash 的 readline 函数返回时执行指定的 BPF_KRETPROBE 函数,即 printret 函数。</p>
|
||||
<p>在 printret 函数中,我们首先获取了调用 readline 函数的进程的进程名称和进程 ID,然后通过 bpf_probe_read_user_str 函数读取了用户输入的命令行字符串,最后通过 bpf_printk 函数打印出进程 ID、进程名称和输入的命令行字符串。</p>
|
||||
<p>除此之外,我们还需要通过 SEC 宏来定义 uprobe 探针,并使用 BPF_KRETPROBE 宏来定义探针函数。</p>
|
||||
<p>在 SEC 宏中,我们需要指定 uprobe 的类型、要捕获的二进制文件的路径和要捕获的函数名称。例如,上面的代码中的 SEC 宏的定义如下:</p>
|
||||
<pre><code class="language-c">SEC("uprobe//bin/bash:readline")
|
||||
<pre><code class="language-c">SEC("uprobe//bin/bash:readline")
|
||||
</code></pre>
|
||||
<p>这表示我们要捕获的是 /bin/bash 二进制文件中的 readline 函数。</p>
|
||||
<p>接下来,我们需要使用 BPF_KRETPROBE 宏来定义探针函数,例如:</p>
|
||||
@@ -244,7 +244,7 @@ char LICENSE[] SEC("license") = "GPL";
|
||||
<pre><code class="language-c"> bpf_probe_read_user_str(str, sizeof(str), ret);
|
||||
</code></pre>
|
||||
<p>最后使用 bpf_printk 函数输出 PID、任务名称和用户输入的字符串。</p>
|
||||
<pre><code class="language-c"> bpf_printk("PID %d (%s) read: %s ", pid, comm, str);
|
||||
<pre><code class="language-c"> bpf_printk("PID %d (%s) read: %s ", pid, comm, str);
|
||||
</code></pre>
|
||||
<p>eunomia-bpf 是一个结合 Wasm 的开源 eBPF 动态加载运行时和开发工具链,它的目的是简化 eBPF 程序的开发、构建、分发、运行。可以参考 <a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a> 下载和安装 ecc 编译工具链和 ecli 运行时。我们使用 eunomia-bpf 编译运行这个例子。</p>
|
||||
<p>编译运行上述代码:</p>
|
||||
|
||||
@@ -198,7 +198,7 @@ struct {
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u32);
|
||||
__type(value, struct event);
|
||||
} values SEC(".maps");
|
||||
} values SEC(".maps");
|
||||
|
||||
|
||||
static int probe_entry(pid_t tpid, int sig)
|
||||
@@ -228,9 +228,9 @@ static int probe_exit(void *ctx, int ret)
|
||||
return 0;
|
||||
|
||||
eventp->ret = ret;
|
||||
bpf_printk("PID %d (%s) sent signal %d ",
|
||||
bpf_printk("PID %d (%s) sent signal %d ",
|
||||
eventp->pid, eventp->comm, eventp->sig);
|
||||
bpf_printk("to PID %d, ret = %d",
|
||||
bpf_printk("to PID %d, ret = %d",
|
||||
eventp->tpid, ret);
|
||||
|
||||
cleanup:
|
||||
@@ -238,7 +238,7 @@ cleanup:
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_kill")
|
||||
SEC("tracepoint/syscalls/sys_enter_kill")
|
||||
int kill_entry(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
pid_t tpid = (pid_t)ctx->args[0];
|
||||
@@ -247,13 +247,13 @@ int kill_entry(struct trace_event_raw_sys_enter *ctx)
|
||||
return probe_entry(tpid, sig);
|
||||
}
|
||||
|
||||
SEC("tracepoint/syscalls/sys_exit_kill")
|
||||
SEC("tracepoint/syscalls/sys_exit_kill")
|
||||
int kill_exit(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
return probe_exit(ctx, ctx->ret);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
</code></pre>
|
||||
<p>上面的代码定义了一个 eBPF 程序,用于捕获进程发送信号的系统调用,包括 kill、tkill 和 tgkill。它通过使用 tracepoint 来捕获系统调用的进入和退出事件,并在这些事件发生时执行指定的探针函数,例如 probe_entry 和 probe_exit。</p>
|
||||
<p>在探针函数中,我们使用 bpf_map 存储捕获的事件信息,包括发送信号的进程 ID、接收信号的进程 ID、信号值和进程的可执行文件名称。在系统调用退出时,我们将获取存储在 bpf_map 中的事件信息,并使用 bpf_printk 打印进程 ID、进程名称、发送的信号和系统调用的返回值。</p>
|
||||
@@ -284,7 +284,7 @@ Runing eBPF program...
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u32);
|
||||
__type(value, struct event);
|
||||
} values SEC(".maps");
|
||||
} values SEC(".maps");
|
||||
</code></pre>
|
||||
<p>并使用一些对应的 API 进行访问,例如 bpf_map_lookup_elem、bpf_map_update_elem、bpf_map_delete_elem 等。</p>
|
||||
<p>更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a></p>
|
||||
|
||||
@@ -202,15 +202,15 @@ struct event {
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "execsnoop.h"
|
||||
#include "execsnoop.h"
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_execve")
|
||||
SEC("tracepoint/syscalls/sys_enter_execve")
|
||||
int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx)
|
||||
{
|
||||
u64 id;
|
||||
@@ -232,7 +232,7 @@ int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这段代码定义了个 eBPF 程序,用于捕获进程执行 execve 系统调用的入口。</p>
|
||||
<p>在入口程序中,我们首先获取了当前进程的进程 ID 和用户 ID,然后通过 bpf_get_current_task 函数获取了当前进程的 task_struct 结构体,并通过 bpf_probe_read_str 函数读取了进程名称。最后,我们通过 bpf_perf_event_output 函数将进程执行事件输出到 perf buffer。</p>
|
||||
@@ -261,7 +261,7 @@ TIME PID PPID UID COMM
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
} events SEC(".maps");
|
||||
</code></pre>
|
||||
<p>就可以往用户态直接发送信息。</p>
|
||||
<p>更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档:<a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a></p>
|
||||
|
||||
@@ -214,20 +214,20 @@ struct event {
|
||||
#endif /* __BOOTSTRAP_H */
|
||||
</code></pre>
|
||||
<p>源文件:exitsnoop.bpf.c</p>
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
<pre><code class="language-c">#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "exitsnoop.h"
|
||||
#include "exitsnoop.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
} rb SEC(".maps");
|
||||
|
||||
SEC("tp/sched/sched_process_exit")
|
||||
SEC("tp/sched/sched_process_exit")
|
||||
int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
{
|
||||
struct task_struct *task;
|
||||
@@ -267,7 +267,7 @@ int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
<p>这段代码展示了如何使用 exitsnoop 监控进程退出事件并使用 ring buffer 向用户态打印输出:</p>
|
||||
<ol>
|
||||
<li>首先,我们引入所需的头文件和 exitsnoop.h。</li>
|
||||
<li>定义一个名为 "LICENSE" 的全局变量,内容为 "Dual BSD/GPL",这是 eBPF 程序的许可证要求。</li>
|
||||
<li>定义一个名为 "LICENSE" 的全局变量,内容为 "Dual BSD/GPL",这是 eBPF 程序的许可证要求。</li>
|
||||
<li>定义一个名为 rb 的 BPF_MAP_TYPE_RINGBUF 类型的映射,它将用于将内核空间的数据传输到用户空间。指定 max_entries 为 256 * 1024,代表 ring buffer 的最大容量。</li>
|
||||
<li>定义一个名为 handle_exit 的 eBPF 程序,它将在进程退出事件触发时执行。传入一个名为 ctx 的 trace_event_raw_sched_process_template 结构体指针作为参数。</li>
|
||||
<li>使用 bpf_get_current_pid_tgid() 函数获取当前任务的 PID 和 TID。对于主线程,PID 和 TID 相同;对于子线程,它们是不同的。我们只关心进程(主线程)的退出,因此在 PID 和 TID 不同时返回 0,忽略子线程退出事件。</li>
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
<p>eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。</p>
|
||||
<p>runqlat 是一个 eBPF 工具,用于分析 Linux 系统的调度性能。具体来说,runqlat 用于测量一个任务在被调度到 CPU 上运行之前在运行队列中等待的时间。这些信息对于识别性能瓶颈和提高 Linux 内核调度算法的整体效率非常有用。</p>
|
||||
<h2 id="runqlat-原理"><a class="header" href="#runqlat-原理">runqlat 原理</a></h2>
|
||||
<p>本教程是 eBPF 入门开发实践系列的第九部分,主题是 "捕获进程调度延迟"。在此,我们将介绍一个名为 runqlat 的程序,其作用是以直方图的形式记录进程调度延迟。</p>
|
||||
<p>本教程是 eBPF 入门开发实践系列的第九部分,主题是 "捕获进程调度延迟"。在此,我们将介绍一个名为 runqlat 的程序,其作用是以直方图的形式记录进程调度延迟。</p>
|
||||
<p>Linux 操作系统使用进程来执行所有的系统和用户任务。这些进程可能被阻塞、杀死、运行,或者正在等待运行。处在后两种状态的进程数量决定了 CPU 运行队列的长度。</p>
|
||||
<p>进程有几种可能的状态,如:</p>
|
||||
<ul>
|
||||
@@ -190,7 +190,7 @@
|
||||
<p>等待资源或其他函数信号的进程会处在可中断或不可中断的睡眠状态:进程被置入睡眠状态,直到它需要的资源变得可用。然后,根据睡眠的类型,进程可以转移到可运行状态,或者保持睡眠。</p>
|
||||
<p>即使进程拥有它需要的所有资源,它也不会立即开始运行。它会转移到可运行状态,与其他处在相同状态的进程一起排队。CPU可以在接下来的几秒钟或毫秒内执行这些进程。调度器为 CPU 排列进程,并决定下一个要执行的进程。</p>
|
||||
<p>根据系统的硬件配置,这个可运行队列(称为 CPU 运行队列)的长度可以短也可以长。短的运行队列长度表示 CPU 没有被充分利用。另一方面,如果运行队列长,那么可能意味着 CPU 不够强大,无法执行所有的进程,或者 CPU 的核心数量不足。在理想的 CPU 利用率下,运行队列的长度将等于系统中的核心数量。</p>
|
||||
<p>进程调度延迟,也被称为 "run queue latency",是衡量线程从变得可运行(例如,接收到中断,促使其处理更多工作)到实际在 CPU 上运行的时间。在 CPU 饱和的情况下,你可以想象线程必须等待其轮次。但在其他奇特的场景中,这也可能发生,而且在某些情况下,它可以通过调优减少,从而提高整个系统的性能。</p>
|
||||
<p>进程调度延迟,也被称为 "run queue latency",是衡量线程从变得可运行(例如,接收到中断,促使其处理更多工作)到实际在 CPU 上运行的时间。在 CPU 饱和的情况下,你可以想象线程必须等待其轮次。但在其他奇特的场景中,这也可能发生,而且在某些情况下,它可以通过调优减少,从而提高整个系统的性能。</p>
|
||||
<p>我们将通过一个示例来阐述如何使用 runqlat 工具。这是一个负载非常重的系统:</p>
|
||||
<pre><code class="language-shell"># runqlat
|
||||
Tracing run queue latency... Hit Ctrl-C to end.
|
||||
@@ -213,7 +213,7 @@ Tracing run queue latency... Hit Ctrl-C to end.
|
||||
16384 -> 32767 : 809 |****************************************|
|
||||
32768 -> 65535 : 64 |*** |
|
||||
</code></pre>
|
||||
<p>在这个输出中,我们看到了一个双模分布,一个模在0到15微秒之间,另一个模在16到65毫秒之间。这些模式在分布(它仅仅是 "count" 列的视觉表示)中显示为尖峰。例如,读取一行:在追踪过程中,809个事件落入了16384到32767微秒的范围(16到32毫秒)。</p>
|
||||
<p>在这个输出中,我们看到了一个双模分布,一个模在0到15微秒之间,另一个模在16到65毫秒之间。这些模式在分布(它仅仅是 "count" 列的视觉表示)中显示为尖峰。例如,读取一行:在追踪过程中,809个事件落入了16384到32767微秒的范围(16到32毫秒)。</p>
|
||||
<p>在后续的教程中,我们将深入探讨如何利用 eBPF 对此类指标进行深度跟踪和分析,以更好地理解和优化系统性能。同时,我们也将学习更多关于 Linux 内核调度器、中断处理和 CPU 饱</p>
|
||||
<p>runqlat 的实现利用了 eBPF 程序,它通过内核跟踪点和函数探针来测量进程在运行队列中的时间。当进程被排队时,trace_enqueue 函数会在一个映射中记录时间戳。当进程被调度到 CPU 上运行时,handle_switch 函数会检索时间戳,并计算当前时间与排队时间之间的时间差。这个差值(或 delta)被用于更新进程的直方图,该直方图记录运行队列延迟的分布。该直方图可用于分析 Linux 内核的调度性能。</p>
|
||||
<h2 id="runqlat-代码实现"><a class="header" href="#runqlat-代码实现">runqlat 代码实现</a></h2>
|
||||
@@ -225,10 +225,10 @@ Tracing run queue latency... Hit Ctrl-C to end.
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "runqlat.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "core_fixes.bpf.h"
|
||||
#include "runqlat.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "core_fixes.bpf.h"
|
||||
|
||||
#define MAX_ENTRIES 10240
|
||||
#define TASK_RUNNING 0
|
||||
@@ -245,24 +245,24 @@ struct {
|
||||
__type(key, u32);
|
||||
__type(value, u32);
|
||||
__uint(max_entries, 1);
|
||||
} cgroup_map SEC(".maps");
|
||||
} cgroup_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u32);
|
||||
__type(value, u64);
|
||||
} start SEC(".maps");
|
||||
} start SEC(".maps");
|
||||
|
||||
static struct hist zero;
|
||||
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u32);
|
||||
__type(value, struct hist);
|
||||
} hists SEC(".maps");
|
||||
} hists SEC(".maps");
|
||||
|
||||
static int trace_enqueue(u32 tgid, u32 pid)
|
||||
{
|
||||
@@ -346,7 +346,7 @@ cleanup:
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("raw_tp/sched_wakeup")
|
||||
SEC("raw_tp/sched_wakeup")
|
||||
int BPF_PROG(handle_sched_wakeup, struct task_struct *p)
|
||||
{
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
@@ -355,7 +355,7 @@ int BPF_PROG(handle_sched_wakeup, struct task_struct *p)
|
||||
return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid));
|
||||
}
|
||||
|
||||
SEC("raw_tp/sched_wakeup_new")
|
||||
SEC("raw_tp/sched_wakeup_new")
|
||||
int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p)
|
||||
{
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
@@ -364,13 +364,13 @@ int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p)
|
||||
return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid));
|
||||
}
|
||||
|
||||
SEC("raw_tp/sched_switch")
|
||||
SEC("raw_tp/sched_switch")
|
||||
int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next)
|
||||
{
|
||||
return handle_switch(preempt, prev, next);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这其中定义了一些常量和全局变量,用于过滤对应的追踪目标:</p>
|
||||
<pre><code class="language-c">#define MAX_ENTRIES 10240
|
||||
@@ -390,14 +390,14 @@ const volatile pid_t targ_tgid = 0;
|
||||
__type(key, u32);
|
||||
__type(value, u32);
|
||||
__uint(max_entries, 1);
|
||||
} cgroup_map SEC(".maps");
|
||||
} cgroup_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u32);
|
||||
__type(value, u64);
|
||||
} start SEC(".maps");
|
||||
} start SEC(".maps");
|
||||
|
||||
static struct hist zero;
|
||||
|
||||
@@ -406,7 +406,7 @@ struct {
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u32);
|
||||
__type(value, struct hist);
|
||||
} hists SEC(".maps");
|
||||
} hists SEC(".maps");
|
||||
</code></pre>
|
||||
<p>这些映射包括:</p>
|
||||
<ul>
|
||||
@@ -464,9 +464,9 @@ struct {
|
||||
</ul>
|
||||
<p>这些入口点分别处理不同的调度事件,但都会调用 handle_switch 函数来计算进程的调度延迟并更新直方图数据。</p>
|
||||
<p>最后,程序包含一个许可证声明:</p>
|
||||
<pre><code class="language-c">char LICENSE[] SEC("license") = "GPL";
|
||||
<pre><code class="language-c">char LICENSE[] SEC("license") = "GPL";
|
||||
</code></pre>
|
||||
<p>这一声明指定了 eBPF 程序的许可证类型,这里使用的是 "GPL"。这对于许多内核功能是必需的,因为它们要求 eBPF 程序遵循 GPL 许可证。</p>
|
||||
<p>这一声明指定了 eBPF 程序的许可证类型,这里使用的是 "GPL"。这对于许多内核功能是必需的,因为它们要求 eBPF 程序遵循 GPL 许可证。</p>
|
||||
<h3 id="runqlath"><a class="header" href="#runqlath">runqlat.h</a></h3>
|
||||
<p>然后我们需要定义一个头文件<code>runqlat.h</code>,用来给用户态处理从内核态上报的事件:</p>
|
||||
<pre><code class="language-c">/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
<tr><td>性能事件</td><td>4.9</td><td><a href="https://github.com/torvalds/linux/commit/0515e5999a466dfe6e1924f460da599bb6821487"><code>0515e5999a46</code></a></td><td>BPF_PROG_TYPE_PERF_EVENT</td></tr>
|
||||
<tr><td>cgroup套接字过滤</td><td>4.10</td><td><a href="https://github.com/torvalds/linux/commit/0e33661de493db325435d565a4a722120ae4cbf3"><code>0e33661de493</code></a></td><td>BPF_PROG_TYPE_CGROUP_SKB</td></tr>
|
||||
<tr><td>cgroup套接字修改</td><td>4.10</td><td><a href="https://github.com/torvalds/linux/commit/61023658760032e97869b07d54be9681d2529e77"><code>610236587600</code></a></td><td>BPF_PROG_TYPE_CGROUP_SOCK</td></tr>
|
||||
<tr><td>轻量级隧道(IN)</td><td>4.10</td><td><a href="https://github.com/torvalds/linux/commit/3a0af8fd61f90920f6fa04e4f1e9a6a73c1b4fd2"><code>3a0af8fd61f9</code></a></td><td>BPF_PROG_TYPE_LWT_IN".lightweight tunnel (OUT)</td></tr>
|
||||
<tr><td>轻量级隧道(IN)</td><td>4.10</td><td><a href="https://github.com/torvalds/linux/commit/3a0af8fd61f90920f6fa04e4f1e9a6a73c1b4fd2"><code>3a0af8fd61f9</code></a></td><td>BPF_PROG_TYPE_LWT_IN".lightweight tunnel (OUT)</td></tr>
|
||||
<tr><td>轻量级隧道 (OUT)</td><td>4.10</td><td><a href="https://github.com/torvalds/linux/commit/3a0af8fd61f90920f6fa04e4f1e9a6a73c1b4fd2"><code>3a0af8fd61f9</code></a></td><td>BPF_PROG_TYPE_LWT_OUT</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
@@ -314,7 +314,7 @@ LSM (Linux安全模块) | 5.7 | <a href="https://git
|
||||
<pre><code class="language-sh">git grep -W 'bpf_map_type {' include/uapi/linux/bpf.h
|
||||
</code></pre>
|
||||
<div class="table-wrapper"><table><thead><tr><th>Map 类型</th><th>内核版本</th><th>提交</th><th>枚举</th></tr></thead><tbody>
|
||||
<tr><td>哈希</td><td>3.19</td><td><a href="https://github.com/torvalds/linux/commit/0f8e4bd8a1fc8c4185f1630061d0a1f2d197a475"><code>0f8e4bd8a1fc</code></a></td><td>BPF_MAP_TYPE_HASH".Array</td></tr>
|
||||
<tr><td>哈希</td><td>3.19</td><td><a href="https://github.com/torvalds/linux/commit/0f8e4bd8a1fc8c4185f1630061d0a1f2d197a475"><code>0f8e4bd8a1fc</code></a></td><td>BPF_MAP_TYPE_HASH".Array</td></tr>
|
||||
<tr><td>Prog array</td><td>4.2</td><td><a href="https://github.com/torvalds/linux/commit/04fd61ab36ec065e194ab5e74ae34a5240d992bb"><code>04fd61ab36ec</code></a></td><td>BPF_MAP_TYPE_PROG_ARRAY</td></tr>
|
||||
<tr><td>Perf events</td><td>4.3</td><td><a href="https://github.com/torvalds/linux/commit/ea317b267e9d03a8241893aa176fba7661d07579"><code>ea317b267e9d</code></a></td><td>BPF_MAP_TYPE_PERF_EVENT_ARRAY</td></tr>
|
||||
<tr><td>Per-CPU hash</td><td>4.6</td><td><a href="https://github.com/torvalds/linux/commit/824bd0ce6c7c43a9e1e210abf124958e54d88342"><code>824bd0ce6c7c</code></a></td><td>BPF_MAP_TYPE_PERCPU_HASH</td></tr>
|
||||
@@ -422,7 +422,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_copy_from_user_task()</code></td><td>5.18</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/376040e47334c6dc6a939a32197acceb00fe4acf"><code>376040e47334</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_csum_diff()</code></td><td>4.6</td><td></td><td><a href="https://github.com/torvalds/linux/commit/7d672345ed295b1356a5d9f7111da1d1d7d65867"><code>7d672345ed29</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_csum_level()</code></td><td>5.7</td><td></td><td><a href="https://github.com/torvalds/linux/commit/7cdec54f9713256bb170873a1fc5c75c9127c9d2"><code>7cdec54f9713</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_csum_update()</code></td><td>4.9</td><td></td><td><a href="https://github.com/torvalds/linux/commit/36bbef52c7eb646ed6247055a2acd3851e317857"><code>36bbef52c7eb</code></a>"<code>BPF_FUNC_current_task_under_cgroup()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_csum_update()</code></td><td>4.9</td><td></td><td><a href="https://github.com/torvalds/linux/commit/36bbef52c7eb646ed6247055a2acd3851e317857"><code>36bbef52c7eb</code></a>"<code>BPF_FUNC_current_task_under_cgroup()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_d_path()</code></td><td>5.10</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6e22ab9da79343532cd3cde39df25e5a5478c692"><code>6e22ab9da793</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_dynptr_data()</code></td><td>5.19</td><td></td><td><a href="https://github.com/torvalds/linux/commit/34d4ef5775f776ec4b0d53a02d588bf3195cada6"><code>34d4ef5775f7</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_dynptr_from_mem()</code></td><td>5.19</td><td></td><td><a href="https://github.com/torvalds/linux/commit/263ae152e96253f40c2c276faad8629e096b3bad"><code>263ae152e962</code></a></td></tr>
|
||||
@@ -449,7 +449,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_get_branch_snapshot()</code></td><td>5.16</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/856c02dbce4f8d6a5644083db22c11750aa11481"><code>856c02dbce4f</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_current_ancestor_cgroup_id()</code></td><td>5.6</td><td></td><td><a href="https://github.com/torvalds/linux/commit/b4490c5c4e023f09b7d27c9a9d3e7ad7d09ea6bf"><code>b4490c5c4e02</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_cgroup_classid()</code></td><td>4.3</td><td></td><td><a href="https://github.com/torvalds/linux/commit/8d20aabe1c76cccac544d9fcc3ad7823d9e98a2d"><code>8d20aabe1c76</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_current_cgroup_id()</code></td><td>4.18</td><td></td><td><a href="https://github.com/torvalds/linux/commit/bf6fa2c893c5237b48569a13fa3c673041430b6c"><code>bf6fa2c893c5</code></a>"."<code>BPF_FUNC_get_current_comm()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_current_cgroup_id()</code></td><td>4.18</td><td></td><td><a href="https://github.com/torvalds/linux/commit/bf6fa2c893c5237b48569a13fa3c673041430b6c"><code>bf6fa2c893c5</code></a>"."<code>BPF_FUNC_get_current_comm()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_current_pid_tgid()</code></td><td>4.2</td><td></td><td><a href="https://github.com/torvalds/linux/commit/ffeedafbf0236f03aeb2e8db273b3e5ae5f5bc89"><code>ffeedafbf023</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_current_task()</code></td><td>4.8</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/606274c5abd8e245add01bc7145a8cbb92b69ba8"><code>606274c5abd8</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_get_current_task_btf()</code></td><td>5.11</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/3ca1032ab7ab010eccb107aa515598788f7d93bb"><code>3ca1032ab7ab</code></a></td></tr>
|
||||
@@ -489,7 +489,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td>RPC_FUNC_getsockopt()</td><td>4.15</td><td></td><td><a href="https://github.com/torvalds/linux/commit/cd86d1fd21025fdd6daf23d1288da405e7ad0ec6">cd86d1fd2102</a></td></tr>
|
||||
<tr><td>RPC_FUNC_ima_file_hash()</td><td>5.18</td><td></td><td><a href="https://github.com/torvalds/linux/commit/174b16946e39ebd369097e0f773536c91a8c1a4c">174b16946e39</a></td></tr>
|
||||
<tr><td>RPC_FUNC_ima_inode_hash()</td><td>5.11</td><td></td><td><a href="https://github.com/torvalds/linux/commit/27672f0d280a3f286a410a8db2004f46ace72a17">27672f0d280a</a></td></tr>
|
||||
<tr><td>RPC_FUNC_inode_storage_delete()</td><td>5.10</td><td></td><td><a href="https://github.com/torvalds/linux/commit/8ea636848aca35b9f97c5b5dee30225cf2dd0fe6">8ea636848aca</a>"."<code>BPF_FUNC_inode_storage_get()</code></td></tr>
|
||||
<tr><td>RPC_FUNC_inode_storage_delete()</td><td>5.10</td><td></td><td><a href="https://github.com/torvalds/linux/commit/8ea636848aca35b9f97c5b5dee30225cf2dd0fe6">8ea636848aca</a>"."<code>BPF_FUNC_inode_storage_get()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_jiffies64()</code></td><td>5.5</td><td></td><td><a href="https://github.com/torvalds/linux/commit/5576b991e9c1a11d2cc21c4b94fc75ec27603896"><code>5576b991e9c1</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_kallsyms_lookup_name()</code></td><td>5.16</td><td></td><td><a href="https://github.com/torvalds/linux/commit/d6aef08a872b9e23eecc92d0e92393473b13c497"><code>d6aef08a872b</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_kptr_xchg()</code></td><td>5.19</td><td></td><td><a href="https://github.com/torvalds/linux/commit/c0a5a21c25f37c9fd7b36072f9968cdff1e4aa13"><code>c0a5a21c25f3</code></a></td></tr>
|
||||
@@ -515,7 +515,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td>`BPF_FUNC_msg_cork_bytes()`</td><td>4.17</td><td></td><td><a href="https://github.com/torvalds/linux/commit/91843d540a139eb8070bcff8aa10089164436deb"><code>91843d540a13</code></a></td></tr>
|
||||
<tr><td>`BPF_FUNC_msg_pop_data()`</td><td>5.0</td><td></td><td><a href="https://github.com/torvalds/linux/commit/7246d8ed4dcce23f7509949a77be15fa9f0e3d28"><code>7246d8ed4dcc</code></a></td></tr>
|
||||
<tr><td>`BPF_FUNC_msg_pull_data()`</td><td>4.17</td><td></td><td><a href="https://github.com/torvalds/linux/commit/015632bb30daaaee64e1bcac07570860e0bf3092"><code>015632bb30da</code></a></td></tr>
|
||||
<tr><td>`BPF_FUNC_msg_push_data()`</td><td>4.20</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6fff607e2f14bd7c63c06c464a6f93b8efbabe28"><code>6fff607e2f14</code></a>".<code>BPF_FUNC_msg_redirect_hash()</code></td></tr>
|
||||
<tr><td>`BPF_FUNC_msg_push_data()`</td><td>4.20</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6fff607e2f14bd7c63c06c464a6f93b8efbabe28"><code>6fff607e2f14</code></a>".<code>BPF_FUNC_msg_redirect_hash()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_msg_redirect_map()</code></td><td>4.17</td><td></td><td><a href="https://github.com/torvalds/linux/commit/4f738adba30a7cfc006f605707e7aee847ffefa0"><code>4f738adba30a</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_per_cpu_ptr()</code></td><td>5.10</td><td></td><td><a href="https://github.com/torvalds/linux/commit/eaa6bcb71ef6ed3dc18fc525ee7e293b06b4882b"><code>eaa6bcb71ef6</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_perf_event_output()</code></td><td>4.4</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/a43eec304259a6c637f4014a6d4767159b6a3aa3"><code>a43eec304259</code></a></td></tr>
|
||||
@@ -528,7 +528,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_probe_read_user()</code></td><td>5.5</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47"><code>6ae08ae3dea2</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_probe_read_user_str()</code></td><td>5.5</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/6ae08ae3dea2cfa03dd3665a3c8475c2d429ef47"><code>6ae08ae3dea2</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_probe_read_str()</code></td><td>4.11</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/a5e8c07059d0f0b31737408711d44794928ac218"><code>a5e8c07059d0</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_probe_write_user()</code></td><td>4.8</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/96ae52279594470622ff0585621a13e96b700600"><code>96ae52279594</code></a>"<code>BPF_FUNC_rc_keydown()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_probe_write_user()</code></td><td>4.8</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/96ae52279594470622ff0585621a13e96b700600"><code>96ae52279594</code></a>"<code>BPF_FUNC_rc_keydown()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_rc_pointer_rel()</code></td><td>5.0</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/01d3240a04f4c09392e13c77b54d4423ebce2d72"><code>01d3240a04f4</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_rc_repeat()</code></td><td>4.18</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/f4364dcfc86df7c1ca47b256eaf6b6d0cdd0d936"><code>f4364dcfc86d</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_read_branch_records()</code></td><td>5.6</td><td>GPL</td><td><a href="https://github.com/torvalds/linux/commit/fff7b64355eac6e29b50229ad1512315bc04b44e"><code>fff7b64355ea</code></a></td></tr>
|
||||
@@ -582,7 +582,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_set_retval()</code></td><td>5.18</td><td></td><td><a href="https://github.com/torvalds/linux/commit/b44123b4a3dcad4664d3a0f72c011ffd4c9c4d93"><code>b44123b4a3dc</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_setsockopt()</code></td><td>4.13</td><td></td><td><a href="https://github.com/torvalds/linux/commit/8c4b4c7e9ff0447995750d9329949fa082520269"><code>8c4b4c7e9ff0</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sk_ancestor_cgroup_id()</code></td><td>5.7</td><td></td><td><a href="https://github.com/torvalds/linux/commit/f307fa2cb4c935f7f1ff0aeb880c7b44fb9a642b"><code>f307fa2cb4c9</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sk_assign()</code></td><td>5.6</td><td></td><td><a href="https://github.com/torvalds/linux/commit/cf7fbe660f2dbd738ab58aea8e9b0ca6ad232449"><code>cf7fbe660f2d</code></a>"."<code>BPF_FUNC_sk_cgroup_id()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sk_assign()</code></td><td>5.6</td><td></td><td><a href="https://github.com/torvalds/linux/commit/cf7fbe660f2dbd738ab58aea8e9b0ca6ad232449"><code>cf7fbe660f2d</code></a>"."<code>BPF_FUNC_sk_cgroup_id()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sk_fullsock()</code></td><td>5.1</td><td></td><td><a href="https://github.com/torvalds/linux/commit/46f8bc92758c6259bcf945e9216098661c1587cd"><code>46f8bc92758c</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sk_lookup_tcp()</code></td><td>4.20</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6acc9b432e6714d72d7d77ec7c27f6f8358d0c71"><code>6acc9b432e67</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sk_lookup_udp()</code></td><td>4.20</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6acc9b432e6714d72d7d77ec7c27f6f8358d0c71"><code>6acc9b432e67</code></a></td></tr>
|
||||
@@ -609,7 +609,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_skb_adjust_room()</code></td><td>4.13</td><td></td><td><a href="https://github.com/torvalds/linux/commit/2be7e212d5419a400d051c84ca9fdd083e5aacac"><code>2be7e212d541</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_ancestor_cgroup_id()</code></td><td>4.19</td><td></td><td><a href="https://github.com/torvalds/linux/commit/7723628101aaeb1d723786747529b4ea65c5b5c5"><code>7723628101aa</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_change_head()</code></td><td>4.10</td><td></td><td><a href="https://github.com/torvalds/linux/commit/3a0af8fd61f90920f6fa04e4f1e9a6a73c1b4fd2"><code>3a0af8fd61f9</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_change_proto()</code></td><td>4.8</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6578171a7ff0c31dc73258f93da7407510abf085"><code>6578171a7ff0</code></a><code>。"</code>BPF_FUNC_skb_change_tail()`</td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_change_proto()</code></td><td>4.8</td><td></td><td><a href="https://github.com/torvalds/linux/commit/6578171a7ff0c31dc73258f93da7407510abf085"><code>6578171a7ff0</code></a><code>。"</code>BPF_FUNC_skb_change_tail()`</td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_change_type()</code></td><td>4.8</td><td></td><td><a href="https://github.com/torvalds/linux/commit/d2485c4242a826fdf493fd3a27b8b792965b9b9e"><code>d2485c4242a8</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_cgroup_classid()</code></td><td>5.10</td><td></td><td><a href="https://github.com/torvalds/linux/commit/b426ce83baa7dff947fb354118d3133f2953aac8"><code>b426ce83baa7</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skb_cgroup_id()</code></td><td>4.18</td><td></td><td><a href="https://github.com/torvalds/linux/commit/cb20b08ead401fd17627a36f035c0bf5bfee5567"><code>cb20b08ead40</code></a></td></tr>
|
||||
@@ -636,7 +636,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_skc_to_tcp6_sock()</code></td><td>5.9</td><td></td><td><a href="https://github.com/torvalds/linux/commit/af7ec13833619e17f03aa73a785a2f871da6d66b"><code>af7ec1383361</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skc_to_udp6_sock()</code></td><td>5.9</td><td></td><td><a href="https://github.com/torvalds/linux/commit/0d4fad3e57df2bf61e8ffc8d12a34b1caf9b8835"><code>0d4fad3e57df</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_skc_to_unix_sock()</code></td><td>5.16</td><td></td><td><a href="https://github.com/torvalds/linux/commit/9eeb3aa33ae005526f672b394c1791578463513f"><code>9eeb3aa33ae0</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_snprintf()</code></td><td>5.13</td><td></td><td><a href="https://github.com/torvalds/linux/commit/7b15523a989b63927c2bb08e9b5b0bbc10b58bef"><code>7b15523a989b</code></a>"."<code>BPF_FUNC_snprintf_btf()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_snprintf()</code></td><td>5.13</td><td></td><td><a href="https://github.com/torvalds/linux/commit/7b15523a989b63927c2bb08e9b5b0bbc10b58bef"><code>7b15523a989b</code></a>"."<code>BPF_FUNC_snprintf_btf()</code></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sock_from_file()</code></td><td>5.11</td><td></td><td><a href="https://github.com/torvalds/linux/commit/4f19cab76136e800a3f04d8c9aa4d8e770e3d3d8"><code>4f19cab76136</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sock_hash_update()</code></td><td>4.18</td><td></td><td><a href="https://github.com/torvalds/linux/commit/81110384441a59cff47430f20f049e69b98c17f4"><code>81110384441a</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sock_map_update()</code></td><td>4.14</td><td></td><td><a href="https://github.com/torvalds/linux/commit/174a79ff9515f400b9a6115643dafd62a635b7e6"><code>174a79ff9515</code></a></td></tr>
|
||||
@@ -649,7 +649,7 @@ The list of subcommands supported in your kernel can be found in file
|
||||
<tr><td><code>BPF_FUNC_sys_bpf()</code></td><td>5.14</td><td></td><td><a href="https://github.com/torvalds/linux/commit/79a7f8bdb159d9914b58740f3d31d602a6e4aca8"><code>79a7f8bdb159</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sys_close()</code></td><td>5.14</td><td></td><td><a href="https://github.com/torvalds/linux/commit/3abea089246f76c1517b054ddb5946f3f1dbd2c0"><code>3abea089246f</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sysctl_get_current_value()</code></td><td>5.2</td><td></td><td><a href="https://github.com/torvalds/linux/commit/1d11b3016cec4ed9770b98e82a61708c8f4926e7"><code>1d11b3016cec</code></a></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sysctl_get_name()</code></td><td>5.2</td><td></td><td><a href="https://github.com/torvalds/linux/commit/808649fb787d918a48a360a668ee4ee9023f0c11"><code>808649fb787d</code></a>".</td></tr>
|
||||
<tr><td><code>BPF_FUNC_sysctl_get_name()</code></td><td>5.2</td><td></td><td><a href="https://github.com/torvalds/linux/commit/808649fb787d918a48a360a668ee4ee9023f0c11"><code>808649fb787d</code></a>".</td></tr>
|
||||
<tr><td>格式:只返回翻译后的内容,不包括原文。<code>BPF_FUNC_sysctl_get_new_value()</code>| 5.2| | <a href="https://github.com/torvalds/linux/commit/4e63acdff864654cee0ac5aaeda3913798ee78f6"><code>4e63acdff864</code></a></td><td></td><td></td><td></td></tr>
|
||||
<tr><td><code>BPF_FUNC_sysctl_set_new_value()</code>|5.2| | <a href="https://github.com/torvalds/linux/commit/4e63acdff864654cee0ac5aaeda3913798ee78f6"><code>4e63acdff864</code></a></td><td></td><td></td><td></td></tr>
|
||||
<tr><td><code>BPF_FUNC_tail_call()</code>|4.2| | <a href="https://github.com/torvalds/linux/commit/04fd61ab36ec065e194ab5e74ae34a5240d992bb"><code>04fd61ab36ec</code></a></td><td></td><td></td><td></td></tr>
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
<li><a href="#%E6%95%B0%E6%8D%AE">数据</a>
|
||||
<ul>
|
||||
<li><a href="#1-bpf_probe_read_kernel">1. bpf_probe_read_kernel()</a></li>
|
||||
<li><a href="#2-bpf_probe_read_kernel_strshell">2. bpf_probe_read_kernel_str()".```shell</a></li>
|
||||
<li><a href="#2-bpf_probe_read_kernel_strshell">2. bpf_probe_read_kernel_str()".```shell</a></li>
|
||||
<li><a href="#3-bpf_ktime_get_ns">3. bpf_ktime_get_ns()</a></li>
|
||||
<li><a href="#4-bpf_get_current_pid_tgid">4. bpf_get_current_pid_tgid()</a></li>
|
||||
<li><a href="#5-bpf_get_current_uid_gid">5. bpf_get_current_uid_gid()</a></li>
|
||||
@@ -333,7 +333,7 @@
|
||||
<li><a href="#7-items_lookup_batch">7. items_lookup_batch()</a></li>
|
||||
<li><a href="#8-items_delete_batch">8. items_delete_batch()</a></li>
|
||||
<li><a href="#9-items_update_batch">9. items_update_batch()</a></li>
|
||||
<li><a href="#11-print_linear_hist%E8%AF%AD%E6%B3%95-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone">11. print_linear_hist()".语法: <code>table.print_linear_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)</code></a></li>
|
||||
<li><a href="#11-print_linear_hist%E8%AF%AD%E6%B3%95-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone">11. print_linear_hist()".语法: <code>table.print_linear_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)</code></a></li>
|
||||
<li><a href="#12-open_ring_buffer">12. open_ring_buffer()</a></li>
|
||||
<li><a href="#13-push">13. push()</a></li>
|
||||
<li><a href="#14-pop">14. pop()</a></li>
|
||||
@@ -383,7 +383,7 @@
|
||||
</ul>
|
||||
<p>第一个参数始终是 <code>struct pt_regs *</code>,其余的是函数的参数(如果你不打算使用它们,则不需要指定)。</p>
|
||||
<p>示例代码:
|
||||
<a href="https://github.com/iovisor/bcc/blob/4afa96a71c5dbfc4c507c3355e20baa6c184a3a8/examples/tracing/tcpv4connect.py#L28">code</a>(<a href="https://github.com/iovisor/bcc/blob/5bd0eb21fd148927b078deb8ac29fff2fb044b66/examples/tracing/tcpv4connect_example.txt#L8">输出结果</a>),"."<a href="https://github.com/iovisor/bcc/commit/310ab53710cfd46095c1f6b3e44f1dbc8d1a41d8#diff-8cd1822359ffee26e7469f991ce0ef00R26">code</a> (<a href="https://github.com/iovisor/bcc/blob/3b9679a3bd9b922c736f6061dc65cb56de7e0250/examples/tracing/bitehist_example.txt#L6">output</a>)</p>
|
||||
<a href="https://github.com/iovisor/bcc/blob/4afa96a71c5dbfc4c507c3355e20baa6c184a3a8/examples/tracing/tcpv4connect.py#L28">code</a>(<a href="https://github.com/iovisor/bcc/blob/5bd0eb21fd148927b078deb8ac29fff2fb044b66/examples/tracing/tcpv4connect_example.txt#L8">输出结果</a>),"."<a href="https://github.com/iovisor/bcc/commit/310ab53710cfd46095c1f6b3e44f1dbc8d1a41d8#diff-8cd1822359ffee26e7469f991ce0ef00R26">code</a> (<a href="https://github.com/iovisor/bcc/blob/3b9679a3bd9b922c736f6061dc65cb56de7e0250/examples/tracing/bitehist_example.txt#L6">output</a>)</p>
|
||||
<!--- 这里无法添加搜索链接,因为GitHub目前无法处理"kprobe__"所需的部分词搜索--->
|
||||
<h3 id="2-kretprobes"><a class="header" href="#2-kretprobes">2. kretprobes</a></h3>
|
||||
<p>语法: kretprobe__<em>kernel_function_name</em></p>
|
||||
@@ -404,18 +404,18 @@
|
||||
<p>这是一个宏,用于对由<em>category</em>:<em>event</em>定义的tracepoint进行追踪。</p>
|
||||
<p>tracepoint名称为<code><category>:<event></code>。
|
||||
probe函数名为<code>tracepoint__<category>__<event></code>。</p>
|
||||
<p>参数在一个<code>args</code>结构体中可用,这些参数是tracepoint的参数。列出这些参数的一种方法是在/sys/kernel/debug/tracing/events/<em>category</em>/<em>event</em>/format下查看相关的格式文件。"<code>args</code> 结构体可用于替代 <code>ctx</code>,作为需要上下文作为参数的每个函数中的参数。这包括特别是 <a href="#3-perf_submit">perf_submit()</a>。</p>
|
||||
<p>参数在一个<code>args</code>结构体中可用,这些参数是tracepoint的参数。列出这些参数的一种方法是在/sys/kernel/debug/tracing/events/<em>category</em>/<em>event</em>/format下查看相关的格式文件。"<code>args</code> 结构体可用于替代 <code>ctx</code>,作为需要上下文作为参数的每个函数中的参数。这包括特别是 <a href="#3-perf_submit">perf_submit()</a>。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">TRACEPOINT_PROBE(random, urandom_read) {
|
||||
// args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
|
||||
bpf_trace_printk("%d\\n", args->got_bits);
|
||||
bpf_trace_printk("%d\\n", args->got_bits);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
<p>这会给 <code>random:urandom_read</code> 追踪点注入代码,并打印出追踪点参数 <code>got_bits</code>。
|
||||
在使用 Python API 时,此探针会自动附加到正确的追踪点目标上。
|
||||
对于 C++,可以通过明确指定追踪点目标和函数名来附加此追踪点探针:
|
||||
<code>BPF::attach_tracepoint("random:urandom_read", "tracepoint__random__urandom_read")</code>
|
||||
<code>BPF::attach_tracepoint("random:urandom_read", "tracepoint__random__urandom_read")</code>
|
||||
注意,上面定义的探针函数的名称是 <code>tracepoint__random__urandom_read</code>。</p>
|
||||
<p>实际示例:
|
||||
<a href="https://github.com/iovisor/bcc/blob/a4159da8c4ea8a05a3c6e402451f530d6e5a8b41/examples/tracing/urandomread.py#L19">code</a> (<a href="https://github.com/iovisor/bcc/commit/e422f5e50ecefb96579b6391a2ada7f6367b83c4#diff-41e5ecfae4a3b38de5f4e0887ed160e5R10">output</a>),
|
||||
@@ -428,7 +428,7 @@ probe函数名为<code>tracepoint__<category>__<event></code>。</p>
|
||||
<pre><code class="language-C">int count(struct pt_regs *ctx) {
|
||||
char buf[64];
|
||||
bpf_probe_read_user(&buf, sizeof(buf), (void *)PT_REGS_PARM1(ctx));
|
||||
bpf_trace_printk("%s %d", buf, PT_REGS_PARM2(ctx));
|
||||
bpf_trace_printk("%s %d", buf, PT_REGS_PARM2(ctx));
|
||||
return(0);
|
||||
}
|
||||
</code></pre>
|
||||
@@ -457,7 +457,7 @@ int count(struct pt_regs *ctx) {
|
||||
char path[128];
|
||||
bpf_usdt_readarg(6, ctx, &addr);
|
||||
bpf_probe_read_user(&path, sizeof(path), (void *)addr);
|
||||
bpf_trace_printk("path:%s\\n", path);
|
||||
bpf_trace_printk("path:%s\\n", path);
|
||||
return 0;
|
||||
};
|
||||
</code></pre>
|
||||
@@ -476,7 +476,7 @@ int count(struct pt_regs *ctx) {
|
||||
|
||||
bpf_probe_read_kernel(&prev_tgid, sizeof(prev->tgid), &prev->tgid);
|
||||
bpf_probe_read_kernel(&next_tgid, sizeof(next->tgid), &next->tgid);
|
||||
bpf_trace_printk("%d -> %d\\n", prev_tgid, next_tgid);
|
||||
bpf_trace_printk("%d -> %d\\n", prev_tgid, next_tgid);
|
||||
}
|
||||
</code></pre>
|
||||
<p>这将仪表化sched:sched_switch跟踪点,并打印prev和next tgid。</p>
|
||||
@@ -496,8 +496,8 @@ int count(struct pt_regs *ctx) {
|
||||
<p>第一个参数始终是<code>struct pt_regs *</code>,其余的参数是函数的参数(如果您不打算使用它们,则无需指定)。</p>
|
||||
<p>相应的Python代码:</p>
|
||||
<pre><code class="language-Python">b = BPF(text=bpf_text)
|
||||
execve_fnname = b.get_syscall_fnname("execve")
|
||||
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
|
||||
execve_fnname = b.get_syscall_fnname("execve")
|
||||
b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
|
||||
</code></pre>
|
||||
<p>示例:
|
||||
<a href="https://github.com/iovisor/bcc/blob/552658edda09298afdccc8a4b5e17311a2d8a771/tools/execsnoop.py#L101">code</a> (<a href="https://github.com/iovisor/bcc/blob/552658edda09298afdccc8a4b5e17311a2d8a771/tools/execsnoop_example.txt#L8">output</a>)</p>
|
||||
@@ -547,10 +547,10 @@ b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
|
||||
<p>LSM探针需要至少一个5.7+内核,并设置了以下配置选项:</p>
|
||||
<ul>
|
||||
<li><code>CONFIG_BPF_LSM=y</code></li>
|
||||
<li><code>CONFIG_LSM</code> 逗号分隔的字符串必须包含"bpf"(例如,
|
||||
<code>CONFIG_LSM="lockdown,yama,bpf"</code>)</li>
|
||||
<li><code>CONFIG_LSM</code> 逗号分隔的字符串必须包含"bpf"(例如,
|
||||
<code>CONFIG_LSM="lockdown,yama,bpf"</code>)</li>
|
||||
</ul>
|
||||
<p>原地示例:"<a href="https://github.com/iovisor/bcc/search?q=LSM_PROBE+path%3Atests&type=Code">搜索/tests</a></p>
|
||||
<p>原地示例:"<a href="https://github.com/iovisor/bcc/search?q=LSM_PROBE+path%3Atests&type=Code">搜索/tests</a></p>
|
||||
<h3 id="12-bpf迭代器"><a class="header" href="#12-bpf迭代器">12. BPF迭代器</a></h3>
|
||||
<p>语法: BPF_ITER(target)</p>
|
||||
<p>这是一个宏,用于定义一个bpf迭代器程序的程序签名。参数 <em>target</em> 指定要迭代的内容。</p>
|
||||
@@ -577,7 +577,7 @@ b.attach_kprobe(event=execve_fnname, fn_name="syscall__execve")
|
||||
<p>现场示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=bpf_probe_read_kernel+path%3Aexamples&type=Code">搜索 /examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=bpf_probe_read_kernel+path%3Atools&type=Code">搜索 /tools</a></p>
|
||||
<h3 id="2-bpf_probe_read_kernel_strshell"><a class="header" href="#2-bpf_probe_read_kernel_strshell">2. bpf_probe_read_kernel_str()".```shell</a></h3>
|
||||
<h3 id="2-bpf_probe_read_kernel_strshell"><a class="header" href="#2-bpf_probe_read_kernel_strshell">2. bpf_probe_read_kernel_str()".```shell</a></h3>
|
||||
<p>语法:<code>int bpf_probe_read_kernel_str(void *dst, int size, const void*src)</code></p>
|
||||
<p>返回值:</p>
|
||||
<ul>
|
||||
@@ -693,7 +693,7 @@ int do_trace(void *ctx) {
|
||||
<p>语法:<code>int bpf_trace_printk(const char *fmt, ...)</code></p>
|
||||
<p>返回值:成功时返回0</p>
|
||||
<p>对于通常的trace_pipe (/sys/kernel/debug/tracing/trace_pipe)提供了一个简单的内核printf()功能。这对于一些快速示例是可以接受的,但有一些限制:最多3个参数,只有一个%s,而且trace_pipe是全局共享的,所以并发程序会有冲突输出。更好的接口是通过BPF_PERF_OUTPUT()。注意,与原始内核版本相比,调用这个辅助函数变得更简单,它的第二个参数已经是 <code>fmt_size</code>。</p>
|
||||
<p>原地示例:"<a href="https://github.com/iovisor/bcc/search?q=bpf_trace_printk+path%3Aexamples&type=Code">搜索 /示例</a>, <a href="https://github.com/iovisor/bcc/search?q=bpf_trace_printk+path%3Atools&type=Code">搜索 /工具</a></p>
|
||||
<p>原地示例:"<a href="https://github.com/iovisor/bcc/search?q=bpf_trace_printk+path%3Aexamples&type=Code">搜索 /示例</a>, <a href="https://github.com/iovisor/bcc/search?q=bpf_trace_printk+path%3Atools&type=Code">搜索 /工具</a></p>
|
||||
<h3 id="2-bpf_perf_output"><a class="header" href="#2-bpf_perf_output">2. BPF_PERF_OUTPUT</a></h3>
|
||||
<p>语法:<code>BPF_PERF_OUTPUT(name)</code></p>
|
||||
<p>创建一个BPF表格,通过性能环形缓冲区将自定义事件数据推送到用户空间。这是将每个事件数据推送到用户空间的首选方法。</p>
|
||||
@@ -729,7 +729,7 @@ int hello(struct pt_regs *ctx) {
|
||||
<a href="https://github.com/iovisor/bcc/search?q=perf_submit+path%3Aexamples&type=Code">搜索 /示例</a>, <a href="https://github.com/iovisor/bcc/search?q=perf_submit+path%3Atools&type=Code">搜索 /工具</a></p>
|
||||
<h3 id="4-perf_submit_skb"><a class="header" href="#4-perf_submit_skb">4. perf_submit_skb()</a></h3>
|
||||
<p>语法:<code>int perf_submit_skb((void *)ctx, u32 packet_size, (void*)data, u32 data_size)</code></p>
|
||||
<p>返回值:成功返回0".一种在网络程序类型中可用的BPF_PERF_OUTPUT表的方法,用于将自定义事件数据和数据包缓冲区的前<code>packet_size</code>字节一起提交到用户空间。请参阅BPF_PERF_OUTPUT条目。(最终调用bpf_perf_event_output()函数。)</p>
|
||||
<p>返回值:成功返回0".一种在网络程序类型中可用的BPF_PERF_OUTPUT表的方法,用于将自定义事件数据和数据包缓冲区的前<code>packet_size</code>字节一起提交到用户空间。请参阅BPF_PERF_OUTPUT条目。(最终调用bpf_perf_event_output()函数。)</p>
|
||||
<p>现场示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=perf_submit_skb+path%3Aexamples&type=Code">搜索/examples</a>
|
||||
<a href="https://github.com/iovisor/bcc/search?q=perf_submit_skb+path%3Atools&type=Code">搜索/tools</a></p>
|
||||
@@ -799,12 +799,12 @@ int hello(struct pt_regs *ctx) {
|
||||
<p><code>BPF_F_TABLE</code>是一个变体,最后一个参数采用标志。<code>BPF_TABLE(https://github.com/iovisor/bcc/tree/master.)</code>实际上是`BPF_F_TABLE(<a href="https://github.com/iovisor/bcc/tree/master">https://github.com/iovisor/bcc/tree/master</a>., 0 /<em>flag</em>/)```的包装。</p>
|
||||
<p>方法(稍后讨论):map.lookup()、map.lookup_or_try_init()、map.delete()、map.update()、map.insert()、map.increment()。</p>
|
||||
<p>现场示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_TABLE+path%3Aexamples&type=Code">搜索/examples</a>,"<a href="https://github.com/iovisor/bcc/search?q=BPF_TABLE+path%3Atools&type=Code">搜索 /工具</a></p>
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_TABLE+path%3Aexamples&type=Code">搜索/examples</a>,"<a href="https://github.com/iovisor/bcc/search?q=BPF_TABLE+path%3Atools&type=Code">搜索 /工具</a></p>
|
||||
<h4 id="固定映射"><a class="header" href="#固定映射">固定映射</a></h4>
|
||||
<p>语法: <code>BPF_TABLE_PINNED(_table_type,_key_type, _leaf_type,_name, _max_entries, "/sys/fs/bpf/xyz")</code></p>
|
||||
<p>语法: <code>BPF_TABLE_PINNED(_table_type,_key_type, _leaf_type,_name, _max_entries, "/sys/fs/bpf/xyz")</code></p>
|
||||
<p>如果映射不存在,则创建一个新的映射并将其固定到bpffs作为文件;否则使用已固定到bpffs的映射。类型信息不强制执行,实际的映射类型取决于固定到位置的映射。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">BPF_TABLE_PINNED("hash", u64, u64, ids, 1024, "/sys/fs/bpf/ids");
|
||||
<pre><code class="language-C">BPF_TABLE_PINNED("hash", u64, u64, ids, 1024, "/sys/fs/bpf/ids");
|
||||
</code></pre>
|
||||
<h3 id="2-bpf_hash"><a class="header" href="#2-bpf_hash">2. BPF_HASH</a></h3>
|
||||
<p>语法: <code>BPF_HASH(name [, key_type [, leaf_type [, size]]])</code></p>
|
||||
@@ -814,7 +814,7 @@ int hello(struct pt_regs *ctx) {
|
||||
<pre><code class="language-C">BPF_HASH(start, struct request *);
|
||||
</code></pre>
|
||||
<p>这将创建一个名为<code>start</code>的哈希,其中关键字为<code>struct request *</code>,值默认为u64。此哈希由disksnoop.py示例用于保存每个I/O请求的时间戳,其中关键字是指向struct request的指针,而值是时间戳。</p>
|
||||
<p>这是<code>BPF_TABLE("hash", ...)</code>的包装宏。</p>
|
||||
<p>这是<code>BPF_TABLE("hash", ...)</code>的包装宏。</p>
|
||||
<p>方法(稍后涵盖):map.lookup(),map.lookup_or_try_init(),map.delete(),map.update(),map.insert(),map.increment()。</p>
|
||||
<p>示例中的原位置链接:<a href="https://github.com/iovisor/bcc/search?q=BPF_HASH+path%3Aexamples&type=Code">搜索 /示例</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_HASH+path%3Atools&type=Code">搜索 /工具</a></p>
|
||||
@@ -825,7 +825,7 @@ int hello(struct pt_regs *ctx) {
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">BPF_ARRAY(counts, u64, 32);
|
||||
</code></pre>
|
||||
<p>这将创建一个名为<code>counts</code>的数组,其中有32个存储桶和64位整数值。funccount.py示例使用此数组保存每个函数的调用计数。".这是一个 <code>BPF_TABLE("array", ...)</code> 的包装宏。</p>
|
||||
<p>这将创建一个名为<code>counts</code>的数组,其中有32个存储桶和64位整数值。funccount.py示例使用此数组保存每个函数的调用计数。".这是一个 <code>BPF_TABLE("array", ...)</code> 的包装宏。</p>
|
||||
<p>方法(稍后介绍):map.lookup()、map.update()、map.increment()。注意,所有数组元素都预先分配为零值,无法删除。</p>
|
||||
<p>在当前位置的示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_ARRAY+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
@@ -838,7 +838,7 @@ int hello(struct pt_regs *ctx) {
|
||||
<pre><code class="language-C">BPF_HISTOGRAM(dist);
|
||||
</code></pre>
|
||||
<p>这创建了一个名为 <code>dist</code> 的直方图,默认有 64 个桶,以 int 类型的键索引。</p>
|
||||
<p>这是一个 <code>BPF_TABLE("histgram", ...)</code> 的包装宏。</p>
|
||||
<p>这是一个 <code>BPF_TABLE("histgram", ...)</code> 的包装宏。</p>
|
||||
<p>方法(稍后介绍):map.increment()。</p>
|
||||
<p>在当前位置的示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_HISTOGRAM+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
@@ -850,7 +850,7 @@ int hello(struct pt_regs *ctx) {
|
||||
<pre><code class="language-C">BPF_STACK_TRACE(stack_traces, 1024);
|
||||
</code></pre>
|
||||
<p>这创建了一个名为 <code>stack_traces</code> 的堆栈跟踪映射,最大堆栈跟踪条目数为 1024。</p>
|
||||
<p>这是一个 <code>BPF_TABLE("stacktrace", ...)</code> 的包装宏。</p>
|
||||
<p>这是一个 <code>BPF_TABLE("stacktrace", ...)</code> 的包装宏。</p>
|
||||
<p>方法(稍后介绍):map.get_stackid()。</p>
|
||||
<p>在当前位置的示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_STACK_TRACE+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
@@ -858,11 +858,11 @@ int hello(struct pt_regs *ctx) {
|
||||
<h3 id="6-bpf_perf_array"><a class="header" href="#6-bpf_perf_array">6. BPF_PERF_ARRAY</a></h3>
|
||||
<p>语法:<code>BPF_PERF_ARRAY(name, max_entries)</code></p>
|
||||
<p>创建一个名为 <code>name</code> 的 perf 数组,提供最大条目数,该数必须等于系统 CPU 的数量。这些映射用于获取硬件性能计数器。例如:</p>
|
||||
<pre><code class="language-C">text="""
|
||||
<pre><code class="language-C">text="""
|
||||
BPF_PERF_ARRAY(cpu_cycles, NUM_CPUS);
|
||||
"""
|
||||
b = bcc.BPF(text=text, cflags=["-DNUM_CPUS=%d" % multiprocessing.cpu_count()])
|
||||
b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLES)
|
||||
"""
|
||||
b = bcc.BPF(text=text, cflags=["-DNUM_CPUS=%d" % multiprocessing.cpu_count()])
|
||||
b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLES)
|
||||
</code></pre>
|
||||
<p>这将创建一个名为<code>cpu_cycles</code>的性能数组,条目数量等于CPU核心数。该数组被配置为,稍后调用<code>map.perf_read()</code>将返回从过去某一时刻开始计算的硬件计数器的周期数。每个表只能配置一种类型的硬件计数器。</p>
|
||||
<p>方法(稍后介绍):<code>map.perf_read()</code>。</p>
|
||||
@@ -878,7 +878,7 @@ b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLE
|
||||
<pre><code class="language-C">BPF_PERCPU_HASH(start, struct request *);
|
||||
</code></pre>
|
||||
<p>这将创建名为<code>start</code>的NUM_CPU个哈希,其中键为<code>struct request *</code>,值默认为u64。</p>
|
||||
<p>这是对<code>BPF_TABLE("percpu_hash", ...)</code>的包装宏。</p>
|
||||
<p>这是对<code>BPF_TABLE("percpu_hash", ...)</code>的包装宏。</p>
|
||||
<p>方法(稍后介绍):<code>map.lookup()</code>、<code>map.lookup_or_try_init()</code>、<code>map.delete()</code>、<code>map.update()</code>、<code>map.insert()</code>、<code>map.increment()</code>。</p>
|
||||
<p>现场示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_PERCPU_HASH+path%3Aexamples&type=Code">搜索 /examples</a>,
|
||||
@@ -892,7 +892,7 @@ b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLE
|
||||
<pre><code class="language-C">BPF_PERCPU_ARRAY(counts, u64, 32);
|
||||
</code></pre>
|
||||
<p>这将创建NUM_CPU个名为<code>counts</code>的数组,其中每个数组有32个桶和64位整数值。</p>
|
||||
<p>这是<code>BPF_TABLE("percpu_array", ...)</code>的包装宏。</p>
|
||||
<p>这是<code>BPF_TABLE("percpu_array", ...)</code>的包装宏。</p>
|
||||
<p>方法(稍后介绍):map.lookup(),map.update(),map.increment()。请注意,所有数组元素都预先分配为零值,并且不能被删除。</p>
|
||||
<p>In situ示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_PERCPU_ARRAY+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
@@ -905,14 +905,14 @@ b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLE
|
||||
<pre><code class="language-c">BPF_LPM_TRIE(trie, struct key_v6);
|
||||
</code></pre>
|
||||
<p>这将创建一个名为<code>trie</code>的LPM字典树映射,其中键是<code>struct key_v6</code>,值默认为u64。</p>
|
||||
<p>这是一个对<code>BPF_F_TABLE("lpm_trie", ..., BPF_F_NO_PREALLOC)</code>的包装宏。</p>
|
||||
<p>这是一个对<code>BPF_F_TABLE("lpm_trie", ..., BPF_F_NO_PREALLOC)</code>的包装宏。</p>
|
||||
<p>方法(稍后介绍):map.lookup(),map.lookup_or_try_init(),map.delete(),map.update(),map.insert(),map.increment()。</p>
|
||||
<p>In situ示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_LPM_TRIE+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_LPM_TRIE+path%3Atools&type=Code">搜索/tools</a></p>
|
||||
<h3 id="10-bpf_prog_array"><a class="header" href="#10-bpf_prog_array">10. BPF_PROG_ARRAY</a></h3>
|
||||
<p>语法:<code>BPF_PROG_ARRAY(name, size)</code>。创建一个名为 <code>name</code> 的程序数组,其中包含 <code>size</code> 个条目。数组的每个条目要么是指向一个 bpf 程序的文件描述符,要么是 <code>NULL</code>。该数组作为一个跳转表,以便 bpf 程序可以“尾调用”其他 bpf 程序。</p>
|
||||
<p>这是一个 <code>BPF_TABLE("prog", ...)</code> 的包装宏。</p>
|
||||
<p>这是一个 <code>BPF_TABLE("prog", ...)</code> 的包装宏。</p>
|
||||
<p>方法(稍后介绍):map.call()。</p>
|
||||
<p>实时示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_PROG_ARRAY+path%3Aexamples&type=Code">搜索 /examples</a>,
|
||||
@@ -937,7 +937,7 @@ b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLE
|
||||
<p>实时示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=BPF_CPUMAP+path%3Aexamples&type=Code">搜索 /examples</a>,</p>
|
||||
<h3 id="13-bpf_xskmap"><a class="header" href="#13-bpf_xskmap">13. BPF_XSKMAP</a></h3>
|
||||
<p>语法:<code>BPF_XSKMAP(name, size [, "/sys/fs/bpf/xyz"])</code>。这将创建一个名为<code>name</code>的xsk映射,带有<code>size</code>个条目,并将其固定到bpffs作为一个文件。每个条目表示一个NIC的队列ID。该映射仅在XDP中用于将数据包重定向到AF_XDP套接字。如果AF_XDP套接字绑定到与当前数据包的队列ID不同的队列,则数据包将被丢弃。对于内核v5.3及更高版本,“lookup”方法可用于检查当前数据包的队列ID是否可用于AF_XDP套接字。有关详细信息,请参阅<a href="https://www.kernel.org/doc/html/latest/networking/af_xdp.html">AF_XDP</a>。</p>
|
||||
<p>语法:<code>BPF_XSKMAP(name, size [, "/sys/fs/bpf/xyz"])</code>。这将创建一个名为<code>name</code>的xsk映射,带有<code>size</code>个条目,并将其固定到bpffs作为一个文件。每个条目表示一个NIC的队列ID。该映射仅在XDP中用于将数据包重定向到AF_XDP套接字。如果AF_XDP套接字绑定到与当前数据包的队列ID不同的队列,则数据包将被丢弃。对于内核v5.3及更高版本,“lookup”方法可用于检查当前数据包的队列ID是否可用于AF_XDP套接字。有关详细信息,请参阅<a href="https://www.kernel.org/doc/html/latest/networking/af_xdp.html">AF_XDP</a>。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">BPF_XSKMAP(xsks_map, 8);
|
||||
</code></pre>
|
||||
@@ -948,9 +948,9 @@ b["cpu_cycles"].open_perf_event(b["cpu_cycles"].HW_CPU_CYCLE
|
||||
<p>语法:<code>BPF_ARRAY_OF_MAPS(name, inner_map_name, size)</code></p>
|
||||
<p>这将创建一个带有映射内部类型(BPF_MAP_TYPE_HASH_OF_MAPS)的数组映射,名称为<code>name</code>,包含<code>size</code>个条目。映射的内部元数据由映射<code>inner_map_name</code>提供,可以是除了<code>BPF_MAP_TYPE_PROG_ARRAY</code>、<code>BPF_MAP_TYPE_CGROUP_STORAGE</code>和<code>BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE</code>之外的大多数数组或哈希映射。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">BPF_TABLE("hash", int, int, ex1, 1024);
|
||||
BPF_TABLE("hash", int, int, ex2, 1024);
|
||||
BPF_ARRAY_OF_MAPS(maps_array, "ex1", 10);
|
||||
<pre><code class="language-C">BPF_TABLE("hash", int, int, ex1, 1024);
|
||||
BPF_TABLE("hash", int, int, ex2, 1024);
|
||||
BPF_ARRAY_OF_MAPS(maps_array, "ex1", 10);
|
||||
</code></pre>
|
||||
<h3 id="15-bpf_hash_of_maps"><a class="header" href="#15-bpf_hash_of_maps">15. BPF_HASH_OF_MAPS</a></h3>
|
||||
<p>语法:<code>BPF_HASH_OF_MAPS(name, key_type, inner_map_name, size)</code></p>
|
||||
@@ -958,7 +958,7 @@ BPF_ARRAY_OF_MAPS(maps_array, "ex1", 10);
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">BPF_ARRAY(ex1, int, 1024);
|
||||
BPF_ARRAY(ex2, int, 1024);
|
||||
BPF_HASH_OF_MAPS(maps_hash, struct custom_key, "ex1", 10);
|
||||
BPF_HASH_OF_MAPS(maps_hash, struct custom_key, "ex1", 10);
|
||||
</code></pre>
|
||||
<h3 id="16-bpf_stack"><a class="header" href="#16-bpf_stack">16. BPF_STACK</a></h3>
|
||||
<p>语法:<code>BPF_STACK(name, leaf_type, max_entries[, flags])</code>。创建一个名为 <code>name</code> 的堆栈,其值类型为 <code>leaf_type</code>,最大条目数为 <code>max_entries</code>。
|
||||
@@ -1021,7 +1021,7 @@ BPF_HASH(skh, struct sock_key, 65535);
|
||||
<h3 id="22-mapupdate"><a class="header" href="#22-mapupdate">22. map.update()</a></h3>
|
||||
<p>语法:<code>map.update(&key, &val)</code></p>
|
||||
<p>将第二个参数中的值与键关联,覆盖任何先前的值。</p>
|
||||
<p>示例:"<a href="https://github.com/iovisor/bcc/search?q=update+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
<p>示例:"<a href="https://github.com/iovisor/bcc/search?q=update+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=update+path%3Atools&type=Code">搜索/tools</a></p>
|
||||
<h3 id="23-mapinsert"><a class="header" href="#23-mapinsert">23. map.insert()</a></h3>
|
||||
<p>语法: <code>map.insert(&key, &val)</code></p>
|
||||
@@ -1045,7 +1045,7 @@ BPF_HASH(skh, struct sock_key, 65535);
|
||||
<a href="https://github.com/iovisor/bcc/search?q=get_stackid+path%3Atools&type=Code">搜索/tools</a></p>
|
||||
<h3 id="26-mapperf_read"><a class="header" href="#26-mapperf_read">26. map.perf_read()</a></h3>
|
||||
<p>语法: <code>u64 map.perf_read(u32 cpu)</code></p>
|
||||
<p>现场示例:""<a href="https://github.com/iovisor/bcc/search?q=perf_read+path%3Atests&type=Code">搜索/tests</a></p>
|
||||
<p>现场示例:""<a href="https://github.com/iovisor/bcc/search?q=perf_read+path%3Atests&type=Code">搜索/tests</a></p>
|
||||
<h3 id="27-mapcall"><a class="header" href="#27-mapcall">27. map.call()</a></h3>
|
||||
<p>语法:<code>void map.call(void *ctx, int index)</code></p>
|
||||
<p>这将调用<code>bpf_tail_call()</code>来尾调用<a href="#10-bpf_prog_array">BPF_PROG_ARRAY</a>中指向<code>index</code>入口的bpf程序。尾调用与普通调用不同。它在跳转到另一个bpf程序后重用当前的栈帧,并且不会返回。如果<code>index</code>入口为空,它将不会跳转到任何地方,程序的执行将会继续进行。</p>
|
||||
@@ -1053,21 +1053,21 @@ BPF_HASH(skh, struct sock_key, 65535);
|
||||
<pre><code class="language-C">BPF_PROG_ARRAY(prog_array, 10);
|
||||
|
||||
int tail_call(void *ctx) {
|
||||
bpf_trace_printk("尾调用\n");
|
||||
bpf_trace_printk("尾调用\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int do_tail_call(void *ctx) {
|
||||
bpf_trace_printk("原始的程序\n");
|
||||
bpf_trace_printk("原始的程序\n");
|
||||
prog_array.call(ctx, 2);
|
||||
return 0;
|
||||
}
|
||||
</code></pre>
|
||||
<pre><code class="language-Python">b = BPF(src_file="example.c")
|
||||
tail_fn = b.load_func("tail_call", BPF.KPROBE)
|
||||
prog_array = b.get_table("prog_array")
|
||||
<pre><code class="language-Python">b = BPF(src_file="example.c")
|
||||
tail_fn = b.load_func("tail_call", BPF.KPROBE)
|
||||
prog_array = b.get_table("prog_array")
|
||||
prog_array[c_int(2)] = c_int(tail_fn.fd)
|
||||
b.attach_kprobe(event="some_kprobe_event", fn_name="do_tail_call")
|
||||
b.attach_kprobe(event="some_kprobe_event", fn_name="do_tail_call")
|
||||
</code></pre>
|
||||
<p>这将<code>tail_call()</code>分配给<code>prog_array[2]</code>。在<code>do_tail_call()</code>的最后,<code>prog_array.call(ctx, 2)</code>尾调用<code>tail_call()</code>并执行它。</p>
|
||||
<p>**注意:**为了防止无限循环,尾调用的最大数量是32(<a href="https://github.com/torvalds/linux/search?l=C&q=MAX_TAIL_CALL_CNT+path%3Ainclude%2Flinux&type=Code"><code>MAX_TAIL_CALL_CNT</code></a>)。</p>
|
||||
@@ -1075,7 +1075,7 @@ b.attach_kprobe(event="some_kprobe_event", fn_name="do_tail_call&
|
||||
<a href="https://github.com/iovisor/bcc/search?l=C&q=call+path%3Aexamples&type=Code">搜索/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?l=C&q=call+path%3Atests&type=Code">搜索/tests</a></p>
|
||||
<h3 id="28-mapredirect_map"><a class="header" href="#28-mapredirect_map">28. map.redirect_map()</a></h3>
|
||||
<p>语法:<code>int map.redirect_map(int index, int flags)</code>".这将根据 <code>index</code> 条目重定向传入的数据包。如果映射是 <a href="#11-bpf_devmap">BPF_DEVMAP</a>,数据包将被发送到该条目指向的网络接口的传输队列。如果映射是 <a href="#12-bpf_cpumap">BPF_CPUMAP</a>,数据包将被发送到<code>index</code> CPU的环形缓冲区,并稍后由CPU处理。如果映射是 <a href="#13-bpf_xskmap">BPF_XSKMAP</a>,数据包将被发送到连接到队列的 AF_XDP 套接字。</p>
|
||||
<p>语法:<code>int map.redirect_map(int index, int flags)</code>".这将根据 <code>index</code> 条目重定向传入的数据包。如果映射是 <a href="#11-bpf_devmap">BPF_DEVMAP</a>,数据包将被发送到该条目指向的网络接口的传输队列。如果映射是 <a href="#12-bpf_cpumap">BPF_CPUMAP</a>,数据包将被发送到<code>index</code> CPU的环形缓冲区,并稍后由CPU处理。如果映射是 <a href="#13-bpf_xskmap">BPF_XSKMAP</a>,数据包将被发送到连接到队列的 AF_XDP 套接字。</p>
|
||||
<p>如果数据包成功被重定向,该函数将返回 XDP_REDIRECT。否则,将返回 XDP_ABORTED 以丢弃该数据包。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-C">BPF_DEVMAP(devmap, 1);
|
||||
@@ -1088,17 +1088,17 @@ int xdp_dummy(struct xdp_md *ctx) {
|
||||
}
|
||||
</code></pre>
|
||||
<pre><code class="language-Python">ip = pyroute2.IPRoute()
|
||||
idx = ip.link_lookup(ifname="eth1")[0]
|
||||
idx = ip.link_lookup(ifname="eth1")[0]
|
||||
|
||||
b = bcc.BPF(src_file="example.c")
|
||||
b = bcc.BPF(src_file="example.c")
|
||||
|
||||
devmap = b.get_table("devmap")
|
||||
devmap = b.get_table("devmap")
|
||||
devmap[c_uint32(0)] = c_int(idx)
|
||||
|
||||
in_fn = b.load_func("redirect_example", BPF.XDP)
|
||||
out_fn = b.load_func("xdp_dummy", BPF.XDP)
|
||||
b.attach_xdp("eth0", in_fn, 0)
|
||||
b.attach_xdp("eth1", out_fn, 0)
|
||||
in_fn = b.load_func("redirect_example", BPF.XDP)
|
||||
out_fn = b.load_func("xdp_dummy", BPF.XDP)
|
||||
b.attach_xdp("eth0", in_fn, 0)
|
||||
b.attach_xdp("eth1", out_fn, 0)
|
||||
</code></pre>
|
||||
<p>示例位置:
|
||||
<a href="https://github.com/iovisor/bcc/search?l=C&q=redirect_map+path%3Aexamples&type=Code">搜索 /examples</a>,</p>
|
||||
@@ -1136,7 +1136,7 @@ BPF_ANY:对于key的条目是否存在,没有条件。
|
||||
<p>实例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=msg_redirect_hash+path%3Atests&type=Code">搜索/tests</a></p>
|
||||
<h3 id="34-mapsk_redirect_hash"><a class="header" href="#34-mapsk_redirect_hash">34. map.sk_redirect_hash()</a></h3>
|
||||
<p>语法:<code>int map.sk_redirect_hash(struct sk_buff *skb, void*key, u64 flags)</code>".This helper is used in programs implementing policies at the skb socket level.
|
||||
<p>语法:<code>int map.sk_redirect_hash(struct sk_buff *skb, void*key, u64 flags)</code>".This helper is used in programs implementing policies at the skb socket level.
|
||||
If the sk_buff skb is allowed to pass (i.e. if the verdict eBPF program returns SK_PASS), redirect it to the socket referenced by map (of type BPF_MAP_TYPE_SOCKHASH) using hash key.
|
||||
Both ingress and egress interfaces can be used for redirection.
|
||||
The BPF_F_INGRESS value in flags is used to make the distinction (ingress path is selected if the flag is present, egress otherwise).
|
||||
@@ -1168,7 +1168,7 @@ Note that it supports multiple words and quotes are not necessary:</p>
|
||||
<p>语法: <code>BPF({text=BPF_program | src_file=filename} [, usdt_contexts=[USDT_object, ...]] [, cflags=[arg1, ...]] [, debug=int])</code></p>
|
||||
<p>创建一个BPF对象。这是定义BPF程序并与其输出交互的主要对象。</p>
|
||||
<p>必须提供<code>text</code>或<code>src_file</code>之一,不能两者都提供。</p>
|
||||
<p><code>cflags</code>指定要传递给编译器的额外参数,例如<code>-DMACRO_NAME=value</code>或<code>-I/include/path</code>。参数以数组形式传递,每个元素为一个额外的参数。注意,字符串不会按空格拆分,所以每个参数必须是数组的不同元素,例如<code>["-include", "header.h"]</code>。</p>
|
||||
<p><code>cflags</code>指定要传递给编译器的额外参数,例如<code>-DMACRO_NAME=value</code>或<code>-I/include/path</code>。参数以数组形式传递,每个元素为一个额外的参数。注意,字符串不会按空格拆分,所以每个参数必须是数组的不同元素,例如<code>["-include", "header.h"]</code>。</p>
|
||||
<p><code>debug</code>标志控制调试输出,可以使用或运算:</p>
|
||||
<ul>
|
||||
<li><code>DEBUG_LLVM_IR = 0x1</code> 编译后的LLVM IR</li>
|
||||
@@ -1179,19 +1179,19 @@ Note that it supports multiple words and quotes are not necessary:</p>
|
||||
<li><code>DEBUG_BTF = 0x20</code> 打印来自<code>libbpf</code>库的消息。</li>
|
||||
</ul>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"# 定义整个BPF程序在一行中:">BPF(text='int do_trace(void *ctx) { bpf_trace_printk("命中!\\n"); return 0; }');
|
||||
<pre><code class="language-Python"# 定义整个BPF程序在一行中:">BPF(text='int do_trace(void *ctx) { bpf_trace_printk("命中!\\n"); return 0; }');
|
||||
|
||||
# 定义程序为一个变量:
|
||||
prog = """
|
||||
prog = """
|
||||
int hello(void *ctx) {
|
||||
bpf_trace_printk("你好,世界!\\n");
|
||||
bpf_trace_printk("你好,世界!\\n");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
"""
|
||||
b = BPF(text=prog)
|
||||
|
||||
# 源文件:
|
||||
b = BPF(src_file = "vfsreadlat.c")
|
||||
b = BPF(src_file = "vfsreadlat.c")
|
||||
|
||||
# 包括一个USDT对象:
|
||||
u = USDT(pid=int(pid))
|
||||
@@ -1199,7 +1199,7 @@ u = USDT(pid=int(pid))
|
||||
b = BPF(text=bpf_text, usdt_contexts=[u])
|
||||
|
||||
# 添加包含路径:
|
||||
u = BPF(text=prog, cflags=["-I/path/to/include"])
|
||||
u = BPF(text=prog, cflags=["-I/path/to/include"])
|
||||
|
||||
|
||||
在原地的示例:
|
||||
@@ -1230,10 +1230,10 @@ b = BPF(text=bpf_text, usdt_contexts=[u])
|
||||
<a href="https://github.com/iovisor/bcc/search?q=USDT+path%3Atools+language%3Apython&type=Code">搜索 /tools</a></p>
|
||||
<h2 id="事件"><a class="header" href="#事件">事件</a></h2>
|
||||
<h3 id="1-attach_kprobe"><a class="header" href="#1-attach_kprobe">1. attach_kprobe()</a></h3>
|
||||
<p>语法: <code>BPF.attach_kprobe(event="event", fn_name="name")</code></p>
|
||||
<p>语法: <code>BPF.attach_kprobe(event="event", fn_name="name")</code></p>
|
||||
<p>通过内核动态跟踪函数入口,来检测内核函数<code>event()</code>,并将我们的C定义的函数<code>name()</code>附加到每次调用内核函数时被调用。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.attach_kprobe(event="sys_clone", fn_name="do_trace")
|
||||
<pre><code class="language-Python">b.attach_kprobe(event="sys_clone", fn_name="do_trace")
|
||||
</code></pre>
|
||||
<p>这将检测内核<code>sys_clone()</code>函数,并在每次调用时运行我们定义的BPF函数<code>do_trace()</code>。</p>
|
||||
<p>您可以多次调用attach_kprobe(),并将您的BPF函数附加到多个内核函数上。您也可以多次调用attach_kprobe()函数将多个BPF函数附加到同一个内核函数。</p>
|
||||
@@ -1242,10 +1242,10 @@ b = BPF(text=bpf_text, usdt_contexts=[u])
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_kprobe+path%3Aexamples+language%3Apython&type=Code">查找/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_kprobe+path%3Atools+language%3Apython&type=Code">查找/tools</a></p>
|
||||
<h3 id="2-attach_kretprobe"><a class="header" href="#2-attach_kretprobe">2. attach_kretprobe()</a></h3>
|
||||
<p>语法:BPF.attach_kretprobe(event="事件", fn_name="名称" [, maxactive=int])</p>
|
||||
<p>语法:BPF.attach_kretprobe(event="事件", fn_name="名称" [, maxactive=int])</p>
|
||||
<p>使用内核动态跟踪函数返回来检测内核函数event()的返回,并附加我们定义的C函数name()在内核函数返回时调用。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.attach_kretprobe(event="vfs_read", fn_name="do_return")
|
||||
<pre><code class="language-Python">b.attach_kretprobe(event="vfs_read", fn_name="do_return")
|
||||
</code></pre>
|
||||
<p>这将检测内核的vfs_read()函数,每次调用该函数时都会执行我们定义的BPF函数do_return()。</p>
|
||||
<p>您可以多次调用attach_kretprobe()函数,并将您的BPF函数附加到多个内核函数的返回值。
|
||||
@@ -1256,11 +1256,11 @@ b = BPF(text=bpf_text, usdt_contexts=[u])
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_kretprobe+path%3Aexamples+language%3Apython&type=Code">查找/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_kretprobe+path%3Atools+language%3Apython&type=Code">查找/tools</a></p>
|
||||
<h3 id="3-attach_tracepoint"><a class="header" href="#3-attach_tracepoint">3. attach_tracepoint()</a></h3>
|
||||
<p>语法:BPF.attach_tracepoint(tp="追踪点", fn_name="名称")</p>
|
||||
<p>语法:BPF.attach_tracepoint(tp="追踪点", fn_name="名称")</p>
|
||||
<p>检测由tracepoint描述的内核追踪点,并在命中时运行BPF函数name()。这是一种显式方式来操控 tracepoints。在前面的 tracepoints 部分讲解过的 <code>TRACEPOINT_PROBE</code> 语法是另一种方法,其优点是自动声明一个包含 tracepoint 参数的 <code>args</code> 结构体。在使用 <code>attach_tracepoint()</code> 时,tracepoint 参数需要在 BPF 程序中声明。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python"># 定义 BPF 程序
|
||||
bpf_text = """
|
||||
bpf_text = """
|
||||
#include <uapi/linux/ptrace.h>
|
||||
|
||||
struct urandom_read_args {
|
||||
@@ -1272,14 +1272,14 @@ struct urandom_read_args {
|
||||
};
|
||||
|
||||
int printarg(struct urandom_read_args *args) {
|
||||
bpf_trace_printk("%d\\n", args->got_bits);
|
||||
bpf_trace_printk("%d\\n", args->got_bits);
|
||||
return 0;
|
||||
};
|
||||
"""
|
||||
"""
|
||||
|
||||
# 加载 BPF 程序
|
||||
b = BPF(text=bpf_text)
|
||||
b.attach_tracepoint("random:urandom_read", "printarg")
|
||||
b.attach_tracepoint("random:urandom_read", "printarg")
|
||||
</code></pre>
|
||||
<p>注意,<code>printarg()</code> 的第一个参数现在是我们定义的结构体。</p>
|
||||
<p>代码示例:
|
||||
@@ -1287,17 +1287,17 @@ b.attach_tracepoint("random:urandom_read", "printarg")
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_tracepoint+path%3Aexamples+language%3Apython&type=Code">search /examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_tracepoint+path%3Atools+language%3Apython&type=Code">search /tools</a></p>
|
||||
<h3 id="4-attach_uprobe"><a class="header" href="#4-attach_uprobe">4. attach_uprobe()</a></h3>
|
||||
<p>语法:<code>BPF.attach_uprobe(name="location", sym="symbol", fn_name="name" [, sym_off=int])</code>, <code>BPF.attach_uprobe(name="location", sym_re="regex", fn_name="name")</code>, <code>BPF.attach_uprobe(name="location", addr=int, fn_name="name")</code></p>
|
||||
<p>语法:<code>BPF.attach_uprobe(name="location", sym="symbol", fn_name="name" [, sym_off=int])</code>, <code>BPF.attach_uprobe(name="location", sym_re="regex", fn_name="name")</code>, <code>BPF.attach_uprobe(name="location", addr=int, fn_name="name")</code></p>
|
||||
<p>用于操控位于 <code>location</code> 中的库或二进制文件中的用户级别函数 <code>symbol()</code>,使用用户级别动态跟踪该函数的入口,并将我们定义的 C 函数 <code>name()</code> 附加为在用户级别函数被调用时调用的函数。如果给定了 <code>sym_off</code>,则该函数将附加到符号的偏移量上。真实的地址<code>addr</code>可以替代<code>sym</code>,在这种情况下,<code>sym</code>必须设置为其默认值。如果文件是非PIE可执行文件,则<code>addr</code>必须是虚拟地址,否则它必须是相对于文件加载地址的偏移量。</p>
|
||||
<p>可以在<code>sym_re</code>中提供普通表达式来代替符号名称。然后,uprobes将附加到与提供的正则表达式匹配的符号。</p>
|
||||
<p>在名字参数中可以给出库名而不带lib前缀,或者给出完整路径(/usr/lib/...)。只能通过完整路径(/bin/sh)给出二进制文件。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.attach_uprobe(name="c", sym="strlen", fn_name="count")
|
||||
<pre><code class="language-Python">b.attach_uprobe(name="c", sym="strlen", fn_name="count")
|
||||
</code></pre>
|
||||
<p>这将在libc中对<code>strlen()</code>函数进行插装,并在调用该函数时调用我们的BPF函数<code>count()</code>。请注意,在<code>libc</code>中的<code>libc</code>中的"lib"是不必要的。</p>
|
||||
<p>这将在libc中对<code>strlen()</code>函数进行插装,并在调用该函数时调用我们的BPF函数<code>count()</code>。请注意,在<code>libc</code>中的<code>libc</code>中的"lib"是不必要的。</p>
|
||||
<p>其他例子:</p>
|
||||
<pre><code class="language-Python">b.attach_uprobe(name="c", sym="getaddrinfo", fn_name="do_entry")
|
||||
b.attach_uprobe(name="/usr/bin/python", sym="main", fn_name="do_main")
|
||||
<pre><code class="language-Python">b.attach_uprobe(name="c", sym="getaddrinfo", fn_name="do_entry")
|
||||
b.attach_uprobe(name="/usr/bin/python", sym="main", fn_name="do_main")
|
||||
</code></pre>
|
||||
<p>您可以多次调用attach_uprobe(),并将BPF函数附加到多个用户级函数。</p>
|
||||
<p>有关如何从BPF工具获取参数的详细信息,请参见上一节uprobes。</p>
|
||||
@@ -1305,17 +1305,17 @@ b.attach_uprobe(name="/usr/bin/python", sym="main", fn_name=
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_uprobe+path%3Aexamples+language%3Apython&type=Code">search /examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_uprobe+path%3Atools+language%3Apython&type=Code">search /tools</a></p>
|
||||
<h3 id="5-attach_uretprobe"><a class="header" href="#5-attach_uretprobe">5. attach_uretprobe()</a></h3>
|
||||
<p>语法: <code>BPF.attach_uretprobe(name="location", sym="symbol", fn_name="name")</code></p>
|
||||
<p>语法: <code>BPF.attach_uretprobe(name="location", sym="symbol", fn_name="name")</code></p>
|
||||
<p>使用用户级动态跟踪从名为<code>location</code>的库或二进制文件中的用户级函数<code>symbol()</code>返回值的方式仪器化,并将我们定义的C函数<code>name()</code>附加到用户级函数返回时调用。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.attach_uretprobe(name="c", sym="strlen", fn_name="count")
|
||||
<pre><code class="language-Python">b.attach_uretprobe(name="c", sym="strlen", fn_name="count")
|
||||
```。这将使用libc库对```strlen()```函数进行插装,并在其返回时调用我们的BPF函数```count()```。
|
||||
|
||||
其他示例:
|
||||
|
||||
```Python
|
||||
b.attach_uretprobe(name="c", sym="getaddrinfo", fn_name="do_return")
|
||||
b.attach_uretprobe(name="/usr/bin/python", sym="main", fn_name="do_main")
|
||||
b.attach_uretprobe(name="c", sym="getaddrinfo", fn_name="do_return")
|
||||
b.attach_uretprobe(name="/usr/bin/python", sym="main", fn_name="do_main")
|
||||
</code></pre>
|
||||
<p>您可以多次调用attach_uretprobe(),并将您的BPF函数附加到多个用户级函数上。</p>
|
||||
<p>有关如何对BPF返回值进行插装的详细信息,请参阅前面的uretprobes部分。</p>
|
||||
@@ -1328,20 +1328,20 @@ b.attach_uretprobe(name="/usr/bin/python", sym="main", fn_na
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"># 根据给定的PID启用USDT探针
|
||||
u = USDT(pid=int(pid))
|
||||
u.enable_probe(probe="http__server__request", fn_name="do_trace")
|
||||
u.enable_probe(probe="http__server__request", fn_name="do_trace")
|
||||
</code></pre>
|
||||
<p>要检查您的二进制文件是否具有USDT探针以及它们的详细信息,可以运行<code>readelf -n binary</code>并检查stap调试部分。</p>
|
||||
<p>内部示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=enable_probe+path%3Aexamples+language%3Apython&type=Code">搜索/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=enable_probe+path%3Atools+language%3Apython&type=Code">搜索/tools</a></p>
|
||||
<h3 id="7-attach_raw_tracepoint"><a class="header" href="#7-attach_raw_tracepoint">7. attach_raw_tracepoint()</a></h3>
|
||||
<p>语法:<code>BPF.attach_raw_tracepoint(tp="tracepoint", fn_name="name")</code></p>
|
||||
<p>语法:<code>BPF.attach_raw_tracepoint(tp="tracepoint", fn_name="name")</code></p>
|
||||
<p>对由<code>tracepoint</code>(仅<code>event</code>,无<code>category</code>)描述的内核原始跟踪点进行插装,并在命中时运行BPF函数<code>name()</code>。</p>
|
||||
<p>这是一种明确的插装跟踪点的方法。早期原始跟踪点部分介绍的<code>RAW_TRACEPOINT_PROBE</code>语法是一种替代方法。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.attach_raw_tracepoint("sched_switch", "do_trace")
|
||||
<pre><code class="language-Python">b.attach_raw_tracepoint("sched_switch", "do_trace")
|
||||
</code></pre>
|
||||
<p>内部示例:"."<a href="https://github.com/iovisor/bcc/search?q=attach_raw_tracepoint+path%3Atools+language%3Apython&type=Code">搜索 /工具</a></p>
|
||||
<p>内部示例:"."<a href="https://github.com/iovisor/bcc/search?q=attach_raw_tracepoint+path%3Atools+language%3Apython&type=Code">搜索 /工具</a></p>
|
||||
<h3 id="8-attach_raw_socket"><a class="header" href="#8-attach_raw_socket">8. attach_raw_socket()</a></h3>
|
||||
<p>语法: <code>BPF.attach_raw_socket(fn, dev)</code></p>
|
||||
<p>将一个BPF函数附加到指定的网络接口。</p>
|
||||
@@ -1356,7 +1356,7 @@ u.enable_probe(probe="http__server__request", fn_name="do_trace&q
|
||||
<p>示例位置:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_raw_socket+path%3Aexamples+language%3Apython&type=Code">搜索 /示例</a></p>
|
||||
<h3 id="9-attach_xdp"><a class="header" href="#9-attach_xdp">9. attach_xdp()</a></h3>
|
||||
<p>语法: <code>BPF.attach_xdp(dev="device", fn=b.load_func("fn_name",BPF.XDP), flags)</code></p>
|
||||
<p>语法: <code>BPF.attach_xdp(dev="device", fn=b.load_func("fn_name",BPF.XDP), flags)</code></p>
|
||||
<p>改装由 <code>dev</code> 描述的网络驱动程序,然后接收数据包,并使用标志运行 BPF 函数 <code>fn_name()</code>。</p>
|
||||
<p>以下是可选的标志列表。</p>
|
||||
<pre><code class="language-Python"># from xdp_flags uapi/linux/if_link.h
|
||||
@@ -1366,8 +1366,8 @@ XDP_FLAGS_DRV_MODE = (1 << 2)
|
||||
XDP_FLAGS_HW_MODE = (1 << 3)
|
||||
XDP_FLAGS_REPLACE = (1 << 4)
|
||||
</code></pre>
|
||||
<p>您可以像这样使用标志: <code>BPF.attach_xdp(dev="device", fn=b.load_func("fn_name",BPF.XDP), flags=BPF.XDP_FLAGS_UPDATE_IF_NOEXIST)</code></p>
|
||||
<p>标志的默认值为0。这意味着如果没有带有 <code>device</code> 的xdp程序,fn将在该设备上运行。如果有一个正在运行的xdp程序与设备关联,旧程序将被新的fn程序替换。".当前,bcc不支持XDP_FLAGS_REPLACE标志。以下是其他标志的描述。</p>
|
||||
<p>您可以像这样使用标志: <code>BPF.attach_xdp(dev="device", fn=b.load_func("fn_name",BPF.XDP), flags=BPF.XDP_FLAGS_UPDATE_IF_NOEXIST)</code></p>
|
||||
<p>标志的默认值为0。这意味着如果没有带有 <code>device</code> 的xdp程序,fn将在该设备上运行。如果有一个正在运行的xdp程序与设备关联,旧程序将被新的fn程序替换。".当前,bcc不支持XDP_FLAGS_REPLACE标志。以下是其他标志的描述。</p>
|
||||
<h4 id="1-xdp_flags_update_if_noexist"><a class="header" href="#1-xdp_flags_update_if_noexist">1. XDP_FLAGS_UPDATE_IF_NOEXIST</a></h4>
|
||||
<p>如果已经将XDP程序附加到指定的驱动程序上,再次附加XDP程序将失败。</p>
|
||||
<h4 id="2-xdp_flags_skb_mode"><a class="header" href="#2-xdp_flags_skb_mode">2. XDP_FLAGS_SKB_MODE</a></h4>
|
||||
@@ -1378,10 +1378,10 @@ XDP程序可以工作,但没有真正的性能优势,因为数据包无论
|
||||
<h4 id="4-xdp_flags_hw_mode"><a class="header" href="#4-xdp_flags_hw_mode">4. XDP_FLAGS_HW_MODE</a></h4>
|
||||
<p>XDP可以直接在NIC上加载和执行 - 只有少数NIC支持这一功能。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.attach_xdp(dev="ens1", fn=b.load_func("do_xdp", BPF.XDP))
|
||||
<pre><code class="language-Python">b.attach_xdp(dev="ens1", fn=b.load_func("do_xdp", BPF.XDP))
|
||||
</code></pre>
|
||||
<p>这将为网络设备<code>ens1</code>安装工具,并在接收数据包时运行我们定义的BPF函数<code>do_xdp()</code>。</p>
|
||||
<p>不要忘记在最后调用<code>b.remove_xdp("ens1")</code>!</p>
|
||||
<p>不要忘记在最后调用<code>b.remove_xdp("ens1")</code>!</p>
|
||||
<p>示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_xdp+path%3Aexamples+language%3Apython&type=Code">搜索/examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=attach_xdp+path%3Atools+language%3Apython&type=Code">搜索/tools</a></p>
|
||||
@@ -1412,20 +1412,20 @@ b.detach_func(fn, map_fd, BPFAttachType.SK_MSG_VERDICT) // 断开 map_fd 上的
|
||||
<p>示例中的内部代码:</p>
|
||||
<p><a href="https://github.com/iovisor/bcc/search?q=detach_func+path%3Aexamples+language%3Apython&type=Code">search /examples</a>,</p>
|
||||
<h3 id="12-detach_kprobe"><a class="header" href="#12-detach_kprobe">12. detach_kprobe()</a></h3>
|
||||
<p>语法:<code>BPF.detach_kprobe(event="event", fn_name="name")</code></p>
|
||||
<p>语法:<code>BPF.detach_kprobe(event="event", fn_name="name")</code></p>
|
||||
<p>断开指定事件的 kprobe 处理函数。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.detach_kprobe(event="__page_cache_alloc", fn_name="trace_func_entry") // 断开 "__page_cache_alloc" 事件上的 "trace_func_entry" 函数
|
||||
<pre><code class="language-Python">b.detach_kprobe(event="__page_cache_alloc", fn_name="trace_func_entry") // 断开 "__page_cache_alloc" 事件上的 "trace_func_entry" 函数
|
||||
</code></pre>
|
||||
<h3 id="13-detach_kretprobe"><a class="header" href="#13-detach_kretprobe">13. detach_kretprobe()</a></h3>
|
||||
<p>语法:<code>BPF.detach_kretprobe(event="event", fn_name="name")</code></p>
|
||||
<p>语法:<code>BPF.detach_kretprobe(event="event", fn_name="name")</code></p>
|
||||
<p>断开指定事件的 kretprobe 处理函数。</p>
|
||||
<p>例如:</p>
|
||||
<pre><code class="language-Python">b.detach_kretprobe(event="__page_cache_alloc", fn_name="trace_func_return") // 断开 "__page_cache_alloc" 事件上的 "trace_func_return" 函数
|
||||
<pre><code class="language-Python">b.detach_kretprobe(event="__page_cache_alloc", fn_name="trace_func_return") // 断开 "__page_cache_alloc" 事件上的 "trace_func_return" 函数
|
||||
</code></pre>
|
||||
<h2 id="调试输出"><a class="header" href="#调试输出">调试输出</a></h2>
|
||||
<h3 id="1-trace_print"><a class="header" href="#1-trace_print">1. trace_print()</a></h3>
|
||||
<p>语法:<code>BPF.trace_print(fmt="fields")</code></p>
|
||||
<p>语法:<code>BPF.trace_print(fmt="fields")</code></p>
|
||||
<p>该方法持续读取全局共享的 <code>/sys/kernel/debug/tracing/trace_pipe</code> 文件并打印其内容。可以通过 BPF 和 <code>bpf_trace_printk()</code> 函数将数据写入该文件,但该方法存在限制,包括缺乏并发跟踪支持。更推荐使用前面介绍的 BPF_PERF_OUTPUT 机制。</p>
|
||||
<p>参数:</p>
|
||||
<ul>
|
||||
@@ -1436,10 +1436,10 @@ b.detach_func(fn, map_fd, BPFAttachType.SK_MSG_VERDICT) // 断开 map_fd 上的
|
||||
b.trace_print()
|
||||
|
||||
# 打印 PID 和消息:
|
||||
b.trace_print(fmt="{1} {5}")
|
||||
b.trace_print(fmt="{1} {5}")
|
||||
</code></pre>
|
||||
<p>示例中的内部代码:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=trace_print+path%3Aexamples+language%3Apython&type=Code">search /examples</a>。"<a href="https://github.com/iovisor/bcc/search?q=trace_print+path%3Atools+language%3Apython&type=Code">搜索 /工具</a></p>
|
||||
<a href="https://github.com/iovisor/bcc/search?q=trace_print+path%3Aexamples+language%3Apython&type=Code">search /examples</a>。"<a href="https://github.com/iovisor/bcc/search?q=trace_print+path%3Atools+language%3Apython&type=Code">搜索 /工具</a></p>
|
||||
<h3 id="2-trace_fields"><a class="header" href="#2-trace_fields">2. trace_fields()</a></h3>
|
||||
<p>语法: <code>BPF.trace_fields(nonblocking=False)</code></p>
|
||||
<p>该方法从全局共享的 /sys/kernel/debug/tracing/trace_pipe 文件中读取一行,并将其作为字段返回。该文件可以通过 BPF 和 bpf_trace_printk() 函数进行写入,但该方法有一些限制,包括缺乏并发追踪支持。我们更推荐使用之前介绍的 BPF_PERF_OUTPUT 机制。</p>
|
||||
@@ -1470,7 +1470,7 @@ b.trace_print(fmt="{1} {5}")
|
||||
<p>timeout 参数是可选的,并以毫秒为单位计量。如果未提供,则轮询将无限期进行。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"># 循环调用带有回调函数 print_event 的 open_perf_buffer
|
||||
b["events"].open_perf_buffer(print_event)
|
||||
b["events"].open_perf_buffer(print_event)
|
||||
while 1:
|
||||
try:
|
||||
b.perf_buffer_poll()
|
||||
@@ -1478,7 +1478,7 @@ while 1:
|
||||
exit()
|
||||
</code></pre>
|
||||
<p>内联示例:
|
||||
<a href="https://github.com/iovisor/bcc/blob/v0.9.0/examples/tracing/hello_perf_output.py#L55">代码</a>"."<a href="https://github.com/iovisor/bcc/search?q=perf_buffer_poll+path%3Aexamples+language%3Apython&type=Code">搜索 /示例</a>,
|
||||
<a href="https://github.com/iovisor/bcc/blob/v0.9.0/examples/tracing/hello_perf_output.py#L55">代码</a>"."<a href="https://github.com/iovisor/bcc/search?q=perf_buffer_poll+path%3Aexamples+language%3Apython&type=Code">搜索 /示例</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=perf_buffer_poll+path%3Atools+language%3Apython&type=Code">搜索 /工具</a></p>
|
||||
<h3 id="2-ring_buffer_poll"><a class="header" href="#2-ring_buffer_poll">2. ring_buffer_poll()</a></h3>
|
||||
<p>语法: <code>BPF.ring_buffer_poll(timeout=T)</code></p>
|
||||
@@ -1486,7 +1486,7 @@ while 1:
|
||||
<p>timeout参数是可选的,以毫秒为单位测量。如果没有指定,轮询将持续到没有更多的数据或回调函数返回负值。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"># 循环使用回调函数print_event
|
||||
b["events"].open_ring_buffer(print_event)
|
||||
b["events"].open_ring_buffer(print_event)
|
||||
while 1:
|
||||
try:
|
||||
b.ring_buffer_poll(30)
|
||||
@@ -1501,7 +1501,7 @@ while 1:
|
||||
<p>与<code>ring_buffer_poll</code>不同,这个方法在尝试消费数据之前<strong>不会轮询数据</strong>。这样可以减少延迟,但会增加CPU消耗。如果不确定使用哪种方法,建议使用<code>ring_buffer_poll</code>。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"># 循环使用回调函数print_event
|
||||
b["events"].open_ring_buffer(print_event)
|
||||
b["events"].open_ring_buffer(print_event)
|
||||
while 1:
|
||||
try:
|
||||
b.ring_buffer_consume()
|
||||
@@ -1513,11 +1513,11 @@ while 1:
|
||||
<h2 id="map-apis"><a class="header" href="#map-apis">Map APIs</a></h2>
|
||||
<p>Maps是BPF数据存储器,在bcc中用于实现表、哈希和直方图等更高层次的对象。</p>
|
||||
<h3 id="1-get_table"><a class="header" href="#1-get_table">1. get_table()</a></h3>
|
||||
<p>语法: <code>BPF.get_table(name)</code>".返回一个table对象。由于可以将表格作为BPF项进行读取,因此此功能不再使用。例如:<code>BPF[name]</code>。</p>
|
||||
<p>语法: <code>BPF.get_table(name)</code>".返回一个table对象。由于可以将表格作为BPF项进行读取,因此此功能不再使用。例如:<code>BPF[name]</code>。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python">counts = b.get_table("counts")
|
||||
<pre><code class="language-Python">counts = b.get_table("counts")
|
||||
|
||||
counts = b["counts"]
|
||||
counts = b["counts"]
|
||||
</code></pre>
|
||||
<p>这两者是等价的。</p>
|
||||
<h3 id="2-open_perf_buffer"><a class="header" href="#2-open_perf_buffer">2. open_perf_buffer()</a></h3>
|
||||
@@ -1530,7 +1530,7 @@ def print_event(cpu, data, size):
|
||||
[...]
|
||||
|
||||
# 循环通过回调函数打印事件
|
||||
b["events"].open_perf_buffer(print_event)
|
||||
b["events"].open_perf_buffer(print_event)
|
||||
while 1:
|
||||
try:
|
||||
b.perf_buffer_poll()
|
||||
@@ -1549,16 +1549,16 @@ BPF_PERF_OUTPUT(events);
|
||||
</code></pre>
|
||||
<p>在Python中,您可以让bcc自动生成C声明中的数据结构(建议方法):</p>
|
||||
<pre><code class="language-Python">def print_event(cpu, data, size):
|
||||
event = b["events"].event(data)
|
||||
event = b["events"].event(data)
|
||||
[...]
|
||||
</code></pre>
|
||||
<p>或者手动定义:</p>
|
||||
<pre><code class="language-Python"># 在Python中定义输出数据结构
|
||||
TASK_COMM_LEN = 16 # linux/sched.h
|
||||
class Data(ct.Structure):
|
||||
_fields_ = [("pid", ct.c_ulonglong),
|
||||
("ts", ct.c_ulonglong),
|
||||
("comm", ct.c_char * TASK_COMM_LEN)]"。def print_event(cpu, data, size):
|
||||
_fields_ = [("pid", ct.c_ulonglong),
|
||||
("ts", ct.c_ulonglong),
|
||||
("comm", ct.c_char * TASK_COMM_LEN)]"。def print_event(cpu, data, size):
|
||||
event = ct.cast(data, ct.POINTER(Data)).contents
|
||||
[...]
|
||||
|
||||
@@ -1578,10 +1578,10 @@ class Data(ct.Structure):
|
||||
|
||||
```Python
|
||||
# 打印输出
|
||||
print("%10s %s" % ("COUNT", "STRING"))
|
||||
counts = b.get_table("counts")
|
||||
print("%10s %s" % ("COUNT", "STRING"))
|
||||
counts = b.get_table("counts")
|
||||
for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
|
||||
print("%10d \"%s\"" % (v.value, k.c.encode('string-escape')))
|
||||
print("%10d \"%s\"" % (v.value, k.c.encode('string-escape')))
|
||||
</code></pre>
|
||||
<p>此示例还使用<code>sorted()</code>方法按值排序。</p>
|
||||
<p>在此处的示例中:
|
||||
@@ -1597,8 +1597,8 @@ for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
|
||||
<pre><code class="language-Python"># 每秒打印映射摘要:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
print("%-8s\n" % time.strftime("%H:%M:%S"), end="")
|
||||
dist.print_log2_hist(sym + " return:")
|
||||
print("%-8s\n" % time.strftime("%H:%M:%S"), end="")
|
||||
dist.print_log2_hist(sym + " return:")
|
||||
dist.clear()
|
||||
</code></pre>
|
||||
<p>在此处的示例中:
|
||||
@@ -1609,10 +1609,10 @@ while True:
|
||||
您应该使用table.items_lookup_and_delete_batch()而不是table.items()后跟table.clear()。它需要内核v5.6。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"># 每秒打印调用率:
|
||||
print("%9s-%9s-%8s-%9s" % ("PID", "COMM", "fname", "counter"))
|
||||
print("%9s-%9s-%8s-%9s" % ("PID", "COMM", "fname", "counter"))
|
||||
while True:
|
||||
for k, v in sorted(b['map'].items_lookup_and_delete_batch(), key=lambda kv: (kv[0]).pid):
|
||||
print("%9s-%9s-%8s-%9d" % (k.pid, k.comm, k.fname, v.counter))
|
||||
print("%9s-%9s-%8s-%9d" % (k.pid, k.comm, k.fname, v.counter))
|
||||
sleep(1)
|
||||
</code></pre>
|
||||
<h3 id="7-items_lookup_batch"><a class="header" href="#7-items_lookup_batch">7. items_lookup_batch()</a></h3>
|
||||
@@ -1621,10 +1621,10 @@ while True:
|
||||
您应该使用table.items_lookup_batch()而不是table.items()。它需要内核v5.6。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"># 打印映射的当前值:
|
||||
print("%9s-%9s-%8s-%9s" % ("PID", "COMM", "fname", "counter"))
|
||||
print("%9s-%9s-%8s-%9s" % ("PID", "COMM", "fname", "counter"))
|
||||
while True:
|
||||
for k, v in sorted(b['map'].items_lookup_batch(), key=lambda kv: (kv[0]).pid):
|
||||
print("%9s-%9s-%8s-%9d" % (k.pid, k.comm, k.fname, v.counter))
|
||||
print("%9s-%9s-%8s-%9d" % (k.pid, k.comm, k.fname, v.counter))
|
||||
</code></pre>
|
||||
<h3 id="8-items_delete_batch"><a class="header" href="#8-items_delete_batch">8. items_delete_batch()</a></h3>
|
||||
<p>语法: <code>table.items_delete_batch(keys)</code></p>
|
||||
@@ -1641,7 +1641,7 @@ while True:
|
||||
<li>keys是要更新的键列表</li>
|
||||
<li>values是包含新值的列表。### 10. print_log2_hist()</li>
|
||||
</ul>
|
||||
<p>语法: <code>table.print_log2_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)</code></p>
|
||||
<p>语法: <code>table.print_log2_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)</code></p>
|
||||
<p>以ASCII的形式打印一个表格作为log2直方图。该表必须以log2的形式存储,可使用BPF函数<code>bpf_log2l()</code>完成。</p>
|
||||
<p>参数:</p>
|
||||
<ul>
|
||||
@@ -1650,7 +1650,7 @@ while True:
|
||||
<li>section_print_fn: 如果section_print_fn不为None,则将传递给bucket值。</li>
|
||||
</ul>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python">b = BPF(text="""
|
||||
<pre><code class="language-Python">b = BPF(text="""
|
||||
BPF_HISTOGRAM(dist);
|
||||
|
||||
int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req)
|
||||
@@ -1658,10 +1658,10 @@ int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req)
|
||||
dist.increment(bpf_log2l(req->__data_len / 1024));
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
""")
|
||||
[...]
|
||||
|
||||
b["dist"].print_log2_hist("kbytes")
|
||||
b["dist"].print_log2_hist("kbytes")
|
||||
</code></pre>
|
||||
<p>输出:</p>
|
||||
<pre><code class="language-sh"> kbytes : count distribution
|
||||
@@ -1679,7 +1679,7 @@ b["dist"].print_log2_hist("kbytes")
|
||||
<p>实际示例:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=print_log2_hist+path%3Aexamples+language%3Apython&type=Code">搜索 /examples</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=print_log2_hist+path%3Atools+language%3Apython&type=Code">搜索 /tools</a></p>
|
||||
<h3 id="11-print_linear_hist语法-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone"><a class="header" href="#11-print_linear_hist语法-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone">11. print_linear_hist()".语法: <code>table.print_linear_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)</code></a></h3>
|
||||
<h3 id="11-print_linear_hist语法-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone"><a class="header" href="#11-print_linear_hist语法-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone">11. print_linear_hist()".语法: <code>table.print_linear_hist(val_type="value", section_header="Bucket ptr", section_print_fn=None)</code></a></h3>
|
||||
<p>以ASCII字符形式打印一个线性直方图的表格。此功能旨在可视化小的整数范围,例如0到100。</p>
|
||||
<p>参数:</p>
|
||||
<ul>
|
||||
@@ -1688,7 +1688,7 @@ b["dist"].print_log2_hist("kbytes")
|
||||
<li>section_print_fn: 如果section_print_fn不为None,则会将bucket的值传递给它。</li>
|
||||
</ul>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python">b = BPF(text="""
|
||||
<pre><code class="language-Python">b = BPF(text="""
|
||||
BPF_HISTOGRAM(dist);
|
||||
|
||||
int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req)
|
||||
@@ -1696,10 +1696,10 @@ int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req)
|
||||
dist.increment(req->__data_len / 1024);
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
""")
|
||||
[...]
|
||||
|
||||
b["dist"].print_linear_hist("kbytes")
|
||||
b["dist"].print_linear_hist("kbytes")
|
||||
</code></pre>
|
||||
<p>输出:</p>
|
||||
<pre><code class="language-sh"> kbytes : count distribution
|
||||
@@ -1736,7 +1736,7 @@ def print_event(ctx, data, size):
|
||||
[...]
|
||||
|
||||
# 循环并使用print_event回调函数
|
||||
b["events"].open_ring_buffer(print_event)
|
||||
b["events"].open_ring_buffer(print_event)
|
||||
while 1:
|
||||
try:
|
||||
b.ring_buffer_poll()
|
||||
@@ -1755,15 +1755,15 @@ BPF_RINGBUF_OUTPUT(events, 8);
|
||||
</code></pre>
|
||||
<p>在Python中,您可以让bcc自动从C的声明中生成数据结构(推荐):</p>
|
||||
<pre><code class="language-Python">def print_event(ctx, data, size):
|
||||
event = b["events"].event(data)
|
||||
event = b["events"].event(data)
|
||||
[...]
|
||||
</code></pre>
|
||||
<p>或者手动定义:</p>
|
||||
<pre><code class="language-Python".# 在Python中定义输出数据结构">TASK_COMM_LEN = 16 # linux/sched.h
|
||||
class Data(ct.Structure):
|
||||
_fields_ = [("pid", ct.c_ulonglong),
|
||||
("ts", ct.c_ulonglong),
|
||||
("comm", ct.c_char * TASK_COMM_LEN)]
|
||||
_fields_ = [("pid", ct.c_ulonglong),
|
||||
("ts", ct.c_ulonglong),
|
||||
("comm", ct.c_char * TASK_COMM_LEN)]
|
||||
|
||||
def print_event(ctx, data, size):
|
||||
event = ct.cast(data, ct.POINTER(Data)).contents
|
||||
@@ -1794,7 +1794,7 @@ def print_event(ctx, data, size):
|
||||
<p>将内核内存地址转换为内核函数名称,并返回该名称。</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-Python"。">格式: 只返回转换后的内容,不包括原始文本。```markdown
|
||||
print("内核函数:" + b.ksym(addr))
|
||||
print("内核函数:" + b.ksym(addr))
|
||||
</code></pre>
|
||||
<p>例子:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=ksym+path%3Aexamples+language%3Apython&type=Code">搜索 /examples</a>,
|
||||
@@ -1803,7 +1803,7 @@ print("内核函数:" + b.ksym(addr))
|
||||
<p>语法:<code>BPF.ksymname(name)</code></p>
|
||||
<p>将内核名称翻译为地址。这是ksym的反向过程。当函数名称未知时,返回-1。</p>
|
||||
<p>例子:</p>
|
||||
<pre><code class="language-Python">print("内核地址:%x" % b.ksymname("vfs_read"))
|
||||
<pre><code class="language-Python">print("内核地址:%x" % b.ksymname("vfs_read"))
|
||||
</code></pre>
|
||||
<p>例子:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=ksymname+path%3Aexamples+language%3Apython&type=Code">搜索 /examples</a>,
|
||||
@@ -1812,7 +1812,7 @@ print("内核函数:" + b.ksym(addr))
|
||||
<p>语法:<code>BPF.sym(addr, pid, show_module=False, show_offset=False)</code></p>
|
||||
<p>将内存地址翻译为pid的函数名称,并返回。小于零的pid将访问内核符号缓存。<code>show_module</code>和<code>show_offset</code>参数控制是否显示函数所在的模块以及是否显示从符号开头的指令偏移量。这些额外参数的默认值为<code>False</code>。</p>
|
||||
<p>例子:</p>
|
||||
<pre><code class="language-python">print("函数:" + b.sym(addr, pid))
|
||||
<pre><code class="language-python">print("函数:" + b.sym(addr, pid))
|
||||
</code></pre>
|
||||
<p>例子:
|
||||
<a href="https://github.com/iovisor/bcc/search?q=sym+path%3Aexamples+language%3Apython&type=Code">搜索 /examples</a>,
|
||||
@@ -1821,19 +1821,19 @@ print("内核函数:" + b.ksym(addr))
|
||||
<p>语法:<code>BPF.num_open_kprobes()</code></p>
|
||||
<p>返回打开的k[ret]probe的数量。当使用event_re附加和分离探测点时,可以发挥作用。不包括perf_events读取器。</p>
|
||||
<p>例子:</p>
|
||||
<pre><code class="language-python">b.attach_kprobe(event_re=pattern, fn_name="trace_count")
|
||||
<pre><code class="language-python">b.attach_kprobe(event_re=pattern, fn_name="trace_count")
|
||||
matched = b.num_open_kprobes()
|
||||
if matched == 0:
|
||||
print("0个函数与\"%s\"匹配。程序退出。" % args.pattern)
|
||||
print("0个函数与\"%s\"匹配。程序退出。" % args.pattern)
|
||||
exit()
|
||||
</code></pre>
|
||||
<p>例子:"<a href="https://github.com/iovisor/bcc/search?q=num_open_kprobes+path%3Aexamples+language%3Apython&type=Code">搜索 /示例</a>,
|
||||
<p>例子:"<a href="https://github.com/iovisor/bcc/search?q=num_open_kprobes+path%3Aexamples+language%3Apython&type=Code">搜索 /示例</a>,
|
||||
<a href="https://github.com/iovisor/bcc/search?q=num_open_kprobes+path%3Atools+language%3Apython&type=Code">搜索 /工具</a></p>
|
||||
<h3 id="5-get_syscall_fnname"><a class="header" href="#5-get_syscall_fnname">5. get_syscall_fnname()</a></h3>
|
||||
<p>语法: <code>BPF.get_syscall_fnname(name : str)</code></p>
|
||||
<p>返回系统调用的相应内核函数名。该辅助函数将尝试不同的前缀,并与系统调用名连接起来。请注意,返回值可能在不同版本的Linux内核中有所不同,有时会引起问题。 (见 <a href="https://github.com/iovisor/bcc/issues/2590">#2590</a>)</p>
|
||||
<p>示例:</p>
|
||||
<pre><code class="language-python">print("在内核中,%s 的函数名是 %s" % ("clone", b.get_syscall_fnname("clone")))
|
||||
<pre><code class="language-python">print("在内核中,%s 的函数名是 %s" % ("clone", b.get_syscall_fnname("clone")))
|
||||
# sys_clone 或 __x64_sys_clone 或 ...
|
||||
</code></pre>
|
||||
<p>现场示例:
|
||||
@@ -1854,14 +1854,14 @@ if matched == 0:
|
||||
R7 invalid mem access 'inv'
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "./tcpaccept", line 179, in <module>
|
||||
File "./tcpaccept", line 179, in <module>
|
||||
b = BPF(text=bpf_text)
|
||||
File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 172, in __init__
|
||||
self._trace_autoload()".
|
||||
/usr/lib/python2.7/dist-packages/bcc/__init__.py",第 612 行,_trace_autoload 中:
|
||||
File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 172, in __init__
|
||||
self._trace_autoload()".
|
||||
/usr/lib/python2.7/dist-packages/bcc/__init__.py",第 612 行,_trace_autoload 中:
|
||||
fn = self.load_func(func_name, BPF.KPROBE)
|
||||
文件 "/usr/lib/python2.7/dist-packages/bcc/__init__.py",第 212 行,load_func 中:
|
||||
raise Exception("加载 BPF 程序 %s 失败" % func_name)
|
||||
文件 "/usr/lib/python2.7/dist-packages/bcc/__init__.py",第 212 行,load_func 中:
|
||||
raise Exception("加载 BPF 程序 %s 失败" % func_name)
|
||||
Exception: 加载 BPF 程序 kretprobe__inet_csk_accept 失败
|
||||
</code></pre>
|
||||
<h2 id="2-无法从专有程序调用-gpl-only-函数"><a class="header" href="#2-无法从专有程序调用-gpl-only-函数">2. 无法从专有程序调用 GPL-only 函数</a></h2>
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
</code></pre>
|
||||
<p>这将以主机的字节序(hexadecimal string)打印出cgroup ID,例如<code>77 16 00 00 01 00 00 00</code>。</p>
|
||||
<pre><code class="language-sh"># FILE=/sys/fs/bpf/test01
|
||||
# CGROUPID_HEX="77 16 00 00 01 00 00 00"
|
||||
# CGROUPID_HEX="77 16 00 00 01 00 00 00"
|
||||
# bpftool map update pinned $FILE key hex $CGROUPID_HEX value hex 00 00 00 00 00 00 00 00 any
|
||||
</code></pre>
|
||||
<p>现在,通过systemd-run启动的shell的cgroup ID已经存在于BPF哈希映射中,bcc工具将显示来自该shell的结果。可以添加和。从BPF哈希映射中删除而不重新启动bcc工具。</p>
|
||||
@@ -230,7 +230,7 @@ else
|
||||
HOST_ENDIAN_CMD=cat
|
||||
fi
|
||||
|
||||
NS_ID_HEX="$(printf '%016x' $(stat -Lc '%i' /proc/self/ns/mnt) | sed 's/.\{2\}/&\n/g' | $HOST_ENDIAN_CMD)"
|
||||
NS_ID_HEX="$(printf '%016x' $(stat -Lc '%i' /proc/self/ns/mnt) | sed 's/.\{2\}/&\n/g' | $HOST_ENDIAN_CMD)"
|
||||
bpftool map update pinned $FILE key hex $NS_ID_HEX value hex 00 00 00 00 any
|
||||
</code></pre>
|
||||
<p>在这个终端中执行命令:</p>
|
||||
|
||||
@@ -294,7 +294,7 @@ TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms)
|
||||
1074 44 13 94.9% 2.9% 1 223
|
||||
2195 170 8 92.5% 6.8% 1 143
|
||||
182 53 56 53.6% 1.3% 1 143
|
||||
62480 40960 20480 40.6% 19.8% 1 223"。
|
||||
62480 40960 20480 40.6% 19.8% 1 223"。
|
||||
格式:仅返回翻译后的内容,不包括原始文本。```
|
||||
7 2 5 22.2% 22.2% 1 223
|
||||
348 0 0 100.0% 0.0% 1 223
|
||||
@@ -328,7 +328,7 @@ PID COMM IP RADDR LADDR LPORT
|
||||
<p>寻找可能指向应用程序配置问题或入侵者的意外连接。</p>
|
||||
<p>更多 <a href="https://github.com/iovisor/bcc/tree/master/tools/tcpaccept_example.txt">示例</a>。</p>
|
||||
<h4 id="19-tcpretrans"><a class="header" href="#19-tcpretrans">1.9. tcpretrans</a></h4>
|
||||
<pre><code class="language-sh"># ./tcpretrans".
|
||||
<pre><code class="language-sh"># ./tcpretrans".
|
||||
```时间 PID IP LADDR:LPORT T> RADDR:RPORT 状态
|
||||
01:55:05 0 4 10.153.223.157:22 R> 69.53.245.40:34619 已建立
|
||||
01:55:05 0 4 10.153.223.157:22 R> 69.53.245.40:34619 已建立
|
||||
@@ -356,7 +356,7 @@ PID COMM IP RADDR LADDR LPORT
|
||||
1024 -> 2047 : 27 |* |
|
||||
2048 -> 4095 : 30 |* |
|
||||
4096 -> 8191 : 20 | |
|
||||
8192 -> 16383 : 29 |* |".16384 -> 32767 : 809 |****************************************|
|
||||
8192 -> 16383 : 29 |* |".16384 -> 32767 : 809 |****************************************|
|
||||
32768 -> 65535 : 64 |*** |
|
||||
</code></pre>
|
||||
<p>这可以帮助量化在CPU饱和期间等待获取CPU的时间损失。</p>
|
||||
@@ -413,9 +413,9 @@ PID COMM IP RADDR LADDR LPORT
|
||||
<h5 id="示例-1"><a class="header" href="#示例-1">示例 1</a></h5>
|
||||
<p>假设您想要跟踪文件所有权更改。有三个系统调用,<code>chown</code>、<code>fchown</code>和<code>lchown</code>,用户可以使用它们来更改文件所有权。相应的系统调用入口是<code>SyS_[f|l]chown</code>。可以使用以下命令打印系统调用参数和调用进程的用户ID。您可以使用<code>id</code>命令查找特定用户的UID。</p>
|
||||
<pre><code class="language-sh">$ trace.py \
|
||||
'p::SyS_chown "file = %s, to_uid = %d, to_gid = %d, from_uid = %d", arg1, arg2, arg3, $uid' \
|
||||
'p::SyS_fchown "fd = %d, to_uid = %d, to_gid = %d, from_uid = %d", arg1, arg2, arg3, $uid' \
|
||||
'p::SyS_lchown "file = %s, to_uid = %d, to_gid = %d, from_uid = %d", arg1, arg2, arg3, $uid'
|
||||
'p::SyS_chown "file = %s, to_uid = %d, to_gid = %d, from_uid = %d", arg1, arg2, arg3, $uid' \
|
||||
'p::SyS_fchown "fd = %d, to_uid = %d, to_gid = %d, from_uid = %d", arg1, arg2, arg3, $uid' \
|
||||
'p::SyS_lchown "file = %s, to_uid = %d, to_gid = %d, from_uid = %d", arg1, arg2, arg3, $uid'
|
||||
PID TID COMM FUNC -
|
||||
1269255 1269255 python3.6 SyS_lchown file = /tmp/dotsync-usisgezu/tmp, to_uid = 128203, to_gid = 100, from_uid = 128203
|
||||
1269441 1269441 zstd SyS_chown file = /tmp/dotsync-vic7ygj0/dotsync-package.zst, to_uid = 128203, to_gid = 100, from_uid = 128203
|
||||
@@ -444,16 +444,16 @@ PID TID COMM FUNC
|
||||
</code></pre>
|
||||
<h5 id="示例-3"><a class="header" href="#示例-3">示例 3</a></h5>
|
||||
<p>此示例与问题 <a href="https://github.com/iovisor/bcc/issues/1231">1231</a> 和 <a href="https://github.com/iovisor/bcc/issues/1516">1516</a> 相关,其中在某些情况下,uprobes 完全无法工作。首先,你可以执行以下 <code>strace</code></p>
|
||||
<pre><code class="language-sh">$ strace trace.py 'r:bash:readline "%s", retval'
|
||||
<pre><code class="language-sh">$ strace trace.py 'r:bash:readline "%s", retval'
|
||||
...
|
||||
perf_event_open(0x7ffd968212f0, -1, 0, -1, 0x8 /* PERF_FLAG_??? */) = -1 EIO (Input/output error)
|
||||
...
|
||||
</code></pre>
|
||||
<p><code>perf_event_open</code>系统调用返回<code>-EIO</code>。在<code>/kernel/trace</code>和<code>/kernel/events</code>目录中查找与<code>EIO</code>相关的内核uprobe代码,函数<code>uprobe_register</code>最可疑。让我们找出是否调用了这个函数,如果调用了,返回值是什么。在一个终端中使用以下命令打印出<code>uprobe_register</code>的返回值:</p>
|
||||
<pre><code class="language-sh">trace.py 'r::uprobe_register "ret = %d", retval'
|
||||
<pre><code class="language-sh">trace.py 'r::uprobe_register "ret = %d", retval'
|
||||
</code></pre>
|
||||
<p>在另一个终端中运行相同的bash uretprobe跟踪示例,您应该得到:</p>
|
||||
<pre><code class="language-sh">$ trace.py 'r::uprobe_register "ret = %d", retval'
|
||||
<pre><code class="language-sh">$ trace.py 'r::uprobe_register "ret = %d", retval'
|
||||
PID TID COMM FUNC -
|
||||
1041401 1041401 python2.7 uprobe_register ret = -5
|
||||
</code></pre>
|
||||
@@ -468,7 +468,7 @@ PID TID COMM FUNC -
|
||||
}
|
||||
</code></pre>
|
||||
<p>为了确认这个理论,使用以下命令找出<code>inode->i_mapping->a_ops</code>的值:</p>
|
||||
<pre><code class="language-sh">$ trace.py -I 'linux/fs.h' 'p::uprobe_register(struct inode *inode) "a_ops = %llx", inode->i_mapping->a_ops'
|
||||
<pre><code class="language-sh">$ trace.py -I 'linux/fs.h' 'p::uprobe_register(struct inode *inode) "a_ops = %llx", inode->i_mapping->a_ops'
|
||||
PID TID COMM FUNC -
|
||||
814288 814288 python2.7 uprobe_register a_ops = ffffffff81a2adc0
|
||||
^C$ grep ffffffff81a2adc0 /proc/kallsyms
|
||||
@@ -476,7 +476,7 @@ ffffffff81a2adc0 R empty_aops
|
||||
</code></pre>
|
||||
<p>内核符号<code>empty_aops</code>没有定义<code>readpage</code>,因此上述可疑条件为真。进一步检查内核源代码显示,<code>overlayfs</code>没有提供自己的<code>a_ops</code>,而其他一些文件系统(例如ext4)定义了自己的<code>a_ops</code>(例如<code>ext4_da_aops</code>),并且<code>ext4_da_aops</code>定义了<code>readpage</code>。因此,uprobe对于ext4正常工作,但在overlayfs上不正常工作。</p>
|
||||
<p>更多<a href="https://github.com/iovisor/bcc/tree/master/tools/trace_example.txt">示例</a>。</p>
|
||||
<h4 id="22-argdist更多示例"><a class="header" href="#22-argdist更多示例">2.2. argdist"。更多<a href="https://github.com/iovisor/bcc/tree/master/tools/argdist_example.txt">示例</a></a></h4>
|
||||
<h4 id="22-argdist更多示例"><a class="header" href="#22-argdist更多示例">2.2. argdist"。更多<a href="https://github.com/iovisor/bcc/tree/master/tools/argdist_example.txt">示例</a></a></h4>
|
||||
<h4 id="23-funccount"><a class="header" href="#23-funccount">2.3. funccount</a></h4>
|
||||
<p>更多<a href="https://github.com/iovisor/bcc/tree/master/tools/funccount_example.txt">示例</a>.</p>
|
||||
<h2 id="网络"><a class="header" href="#网络">网络</a></h2>
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
</code></pre>
|
||||
<p>以下是 hello_world.py 的代码示例:</p>
|
||||
<pre><code class="language-Python">from bcc import BPF
|
||||
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
|
||||
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
|
||||
</code></pre>
|
||||
<p>从中可以学到六件事情:</p>
|
||||
<ol>
|
||||
@@ -211,8 +211,8 @@ BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World
|
||||
</li>
|
||||
</ol>
|
||||
<h3 id="第二课-sys_sync"><a class="header" href="#第二课-sys_sync">第二课 sys_sync()</a></h3>
|
||||
<p>编写一个跟踪 sys_sync() 内核函数的程序。运行时打印 "sys_sync() called"。在跟踪时,在另一个会话中运行 <code>sync</code> 进行测试。hello_world.py 程序中包含了这一切所需的内容。</p>
|
||||
<p>通过在程序刚启动时打印 "Tracing sys_sync()... Ctrl-C to end." 来改进它。提示:它只是 Python 代码。</p>
|
||||
<p>编写一个跟踪 sys_sync() 内核函数的程序。运行时打印 "sys_sync() called"。在跟踪时,在另一个会话中运行 <code>sync</code> 进行测试。hello_world.py 程序中包含了这一切所需的内容。</p>
|
||||
<p>通过在程序刚启动时打印 "Tracing sys_sync()... Ctrl-C to end." 来改进它。提示:它只是 Python 代码。</p>
|
||||
<h3 id="第三课-hello_fieldspy"><a class="header" href="#第三课-hello_fieldspy">第三课 hello_fields.py</a></h3>
|
||||
<p>该程序位于 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/hello_fields.py">examples/tracing/hello_fields.py</a>。样本输出(在另一个会话中运行命令):</p>
|
||||
<pre><code class="language-sh"># examples/tracing/hello_fields.py
|
||||
@@ -226,19 +226,19 @@ BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World
|
||||
<pre><code class="language-Python">from bcc import BPF
|
||||
|
||||
# 定义 BPF 程序
|
||||
prog = """
|
||||
prog = """
|
||||
int hello(void *ctx) {
|
||||
bpf_trace_printk("你好,世界!\\n");
|
||||
bpf_trace_printk("你好,世界!\\n");
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
"""
|
||||
|
||||
# 加载 BPF 程序
|
||||
b = BPF(text=prog)
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
|
||||
|
||||
# 头部
|
||||
print("%-18s %-16s %-6s %s" % ("时间(s)", "进程名", "进程 ID", "消息"))
|
||||
print("%-18s %-16s %-6s %s" % ("时间(s)", "进程名", "进程 ID", "消息"))
|
||||
|
||||
# 格式化输出
|
||||
while 1:
|
||||
@@ -246,7 +246,7 @@ while 1:
|
||||
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
|
||||
except ValueError:
|
||||
continue
|
||||
print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
|
||||
print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
|
||||
</code></pre>
|
||||
<p>这与hello_world.py类似,并通过sys_clone()再次跟踪新进程,但是还有一些要学习的内容:</p>
|
||||
<ol>
|
||||
@@ -257,7 +257,7 @@ while 1:
|
||||
<p><code>hello()</code>:现在我们只是声明了一个C函数,而不是使用<code>kprobe__</code>的快捷方式。我们稍后会引用它。在BPF程序中声明的所有C函数都希望在探测器上执行,因此它们都需要以<code>pt_reg* ctx</code>作为第一个参数。如果您需要定义一些不会在探测器上执行的辅助函数,则需要将其定义为<code>static inline</code>,以便由编译器内联。有时您还需要为其添加<code>_always_inline</code>函数属性。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")</code>:为内核clone系统调用函数创建一个kprobe,该函数将执行我们定义的hello()函数。您可以多次调用attach_kprobe(),并将您的C函数附加到多个内核函数上。</p>
|
||||
<p><code>b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")</code>:为内核clone系统调用函数创建一个kprobe,该函数将执行我们定义的hello()函数。您可以多次调用attach_kprobe(),并将您的C函数附加到多个内核函数上。</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>b.trace_fields()</code>:从trace_pipe中返回一组固定的字段。与trace_print()类似,它对于编写脚本很方便,但是对于实际的工具化需求,我们应该切换到BPF_PERF_OUTPUT()。</p>
|
||||
@@ -267,7 +267,7 @@ while 1:
|
||||
<p>还记得以前系统管理员在缓慢的控制台上输入<code>sync</code>三次然后才重启吗?后来有人认为<code>sync;sync;sync</code>很聪明,将它们都写在一行上运行,尽管这违背了最初的目的!然后,sync变成了同步操作,所以更加愚蠢。无论如何。</p>
|
||||
<p>以下示例计算了<code>do_sync</code>函数被调用的速度,并且如果它在一秒钟之内被调用,则输出信息。<code>sync;sync;sync</code>将为第2个和第3个sync打印输出:</p>
|
||||
<pre><code class="language-sh"># examples/tracing/sync_timing.py
|
||||
追踪快速sync... 按Ctrl-C结束"。
|
||||
追踪快速sync... 按Ctrl-C结束"。
|
||||
</code></pre>
|
||||
<p>在时间0.00秒时:检测到多个同步,上次发生在95毫秒前
|
||||
在时间0.10秒时:检测到多个同步,上次发生在96毫秒前</p>
|
||||
@@ -276,7 +276,7 @@ while 1:
|
||||
from bcc import BPF
|
||||
|
||||
# 加载BPF程序
|
||||
b = BPF(text="""
|
||||
b = BPF(text="""
|
||||
#include <uapi/linux/ptrace.h>
|
||||
|
||||
BPF_HASH(last);
|
||||
@@ -290,7 +290,7 @@ int do_trace(struct pt_regs *ctx) {
|
||||
delta = bpf_ktime_get_ns() - *tsp;
|
||||
if (delta < 1000000000) {
|
||||
// 时间小于1秒则输出
|
||||
bpf_trace_printk("%d\\n", delta / 1000000);
|
||||
bpf_trace_printk("%d\\n", delta / 1000000);
|
||||
}
|
||||
last.delete(&key);
|
||||
}
|
||||
@@ -300,10 +300,10 @@ int do_trace(struct pt_regs *ctx) {
|
||||
last.update(&key, &ts);
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
""")
|
||||
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_trace")
|
||||
print("跟踪快速同步... 按Ctrl-C结束")
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_trace")
|
||||
print("跟踪快速同步... 按Ctrl-C结束")
|
||||
|
||||
# 格式化输出
|
||||
start = 0
|
||||
@@ -312,12 +312,12 @@ while 1:
|
||||
if start == 0:
|
||||
start = ts
|
||||
ts = ts - start
|
||||
print("在时间%.2f秒处:检测到多个同步,上次发生在%s毫秒前" % (ts, ms))
|
||||
print("在时间%.2f秒处:检测到多个同步,上次发生在%s毫秒前" % (ts, ms))
|
||||
</code></pre>
|
||||
<p>学习内容:</p>
|
||||
<ol>
|
||||
<li><code>bpf_ktime_get_ns()</code>: 返回时间,单位为纳秒。</li>
|
||||
<li><code>BPF_HASH(last)</code>: 创建一个BPF映射对象,类型为哈希(关联数组),名为"last"。我们没有指定其他参数,因此默认的键和值类型为u64。</li>
|
||||
<li><code>BPF_HASH(last)</code>: 创建一个BPF映射对象,类型为哈希(关联数组),名为"last"。我们没有指定其他参数,因此默认的键和值类型为u64。</li>
|
||||
<li><code>key = 0</code>: 我们只会在哈希中存储一个键值对,其中键被硬编码为零。</li>
|
||||
<li><code>last.lookup(&key)</code>: 在哈希中查找键,并如果存在则返回其值的指针,否则返回NULL。我们将键作为指针的地址传递给该函数。</li>
|
||||
<li><code>if (tsp != NULL) {</code>: 验证器要求在将从映射查找得到的指针值解引用使用之前,必须先检查其是否为null。1. <code>last.delete(&key)</code>: 从哈希表中删除key。目前需要这样做是因为<a href="https://git.kernel.org/cgit/linux/kernel/git/davem/net.git/commit/?id=a6ed3ea65d9868fdf9eff84e6fe4f666b8d14b02"><code>.update()</code>中存在一个内核错误</a>(在4.8.10中已经修复)。</li>
|
||||
@@ -340,7 +340,7 @@ while 1:
|
||||
REQ_WRITE = 1 # 来自include/linux/blk_types.h
|
||||
|
||||
# 加载BPF程序
|
||||
b = BPF(text="""
|
||||
b = BPF(text="""
|
||||
#include <uapi/linux/ptrace.h>
|
||||
#include <linux/blk-mq.h>
|
||||
|
||||
@@ -359,17 +359,17 @@ void trace_completion(struct pt_regs *ctx, struct request *req) {
|
||||
tsp = start.lookup(&req);
|
||||
if (tsp != 0) {
|
||||
delta = bpf_ktime_get_ns() - *tsp;
|
||||
bpf_trace_printk("%d %x %d\\n", req->__data_len,
|
||||
bpf_trace_printk("%d %x %d\\n", req->__data_len,
|
||||
req->cmd_flags, delta / 1000);
|
||||
start.delete(&req);
|
||||
}
|
||||
}
|
||||
""")
|
||||
""")
|
||||
if BPF.get_kprobe_functions(b'blk_start_request'):
|
||||
b.attach_kprobe(event="blk_start_request", fn_name="trace_start")
|
||||
b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_start")
|
||||
b.attach_kprobe(event="blk_start_request", fn_name="trace_start")
|
||||
b.attach_kprobe(event="blk_mq_start_request", fn_name="trace_start")
|
||||
if BPF.get_kprobe_functions(b'__blk_account_io_done'):
|
||||
b.attach_kprobe(event="__blk_account_io_done", fn_name="trace_completion") else: b.attach_kprobe(event="blk_account_io_done", fn_name="trace_completion")
|
||||
b.attach_kprobe(event="__blk_account_io_done", fn_name="trace_completion") else: b.attach_kprobe(event="blk_account_io_done", fn_name="trace_completion")
|
||||
[...]
|
||||
</code></pre>
|
||||
<p>学习内容:</p>
|
||||
@@ -394,7 +394,7 @@ TIME(s) COMM PID MESSAGE
|
||||
<pre><code class="language-Python">from bcc import BPF
|
||||
|
||||
// 定义BPF程序
|
||||
prog = """
|
||||
prog = """
|
||||
#include <linux/sched.h>
|
||||
|
||||
// 在C中定义输出数据结构
|
||||
@@ -416,40 +416,40 @@ int hello(struct pt_regs *ctx) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
"""
|
||||
|
||||
// 加载BPF程序
|
||||
b = BPF(text=prog)
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
|
||||
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
|
||||
|
||||
//标题
|
||||
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
|
||||
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
|
||||
|
||||
//处理事件
|
||||
start = 0
|
||||
def print_event(cpu, data, size):
|
||||
global start
|
||||
event = b["events"].event(data)
|
||||
event = b["events"].event(data)
|
||||
if start == 0:
|
||||
start = event.ts
|
||||
time_s = (float(event.ts - start)) / 1000000000
|
||||
print("%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid, "你好,perf_output!"))
|
||||
print("%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid, "你好,perf_output!"))
|
||||
|
||||
//循环并回调print_event
|
||||
b["events"].open_perf_buffer(print_event)
|
||||
b["events"].open_perf_buffer(print_event)
|
||||
while 1:
|
||||
b.perf_buffer_poll()
|
||||
</code></pre>
|
||||
<p>学习的内容:</p>
|
||||
<ol>
|
||||
<li><code>struct data_t</code>: 这定义了一个C结构体,我们将用它来从内核传递数据到用户空间。1. <code>BPF_PERF_OUTPUT(events)</code>: 这里给我们的输出通道命名为"events"。</li>
|
||||
<li><code>struct data_t</code>: 这定义了一个C结构体,我们将用它来从内核传递数据到用户空间。1. <code>BPF_PERF_OUTPUT(events)</code>: 这里给我们的输出通道命名为"events"。</li>
|
||||
<li><code>struct data_t data = {};</code>: 创建一个空的<code>data_t</code>结构体,我们将在之后填充它。</li>
|
||||
<li><code>bpf_get_current_pid_tgid()</code>: 返回低32位的进程ID(内核视图中的PID,用户空间中通常被表示为线程ID),以及高32位的线程组ID(用户空间通常认为是PID)。通过直接将其设置为<code>u32</code>,我们丢弃了高32位。应该显示PID还是TGID?对于多线程应用程序,TGID将是相同的,所以如果你想要区分它们,你需要PID。这也是对最终用户期望的一个问题。</li>
|
||||
<li><code>bpf_get_current_comm()</code>: 将当前进程的名称填充到第一个参数的地址中。</li>
|
||||
<li><code>events.perf_submit()</code>: 通过perf环形缓冲区将事件提交给用户空间以供读取。</li>
|
||||
<li><code>def print_event()</code>: 定义一个Python函数来处理从<code>events</code>流中读取的事件。</li>
|
||||
<li><code>b["events"].event(data)</code>: 现在将事件作为一个Python对象获取,该对象是根据C声明自动生成的。</li>
|
||||
<li><code>b["events"].open_perf_buffer(print_event)</code>: 将Python的<code>print_event</code>函数与<code>events</code>流关联起来。</li>
|
||||
<li><code>b["events"].event(data)</code>: 现在将事件作为一个Python对象获取,该对象是根据C声明自动生成的。</li>
|
||||
<li><code>b["events"].open_perf_buffer(print_event)</code>: 将Python的<code>print_event</code>函数与<code>events</code>流关联起来。</li>
|
||||
<li><code>while 1: b.perf_buffer_poll()</code>: 阻塞等待事件。</li>
|
||||
</ol>
|
||||
<h3 id="第八课-sync_perf_outputpy"><a class="header" href="#第八课-sync_perf_outputpy">第八课。 sync_perf_output.py</a></h3>
|
||||
@@ -464,7 +464,7 @@ while 1:
|
||||
2 -> 3 : 0 | |
|
||||
4 -> 7 : 211 |********** |
|
||||
8 -> 15 : 0 | |
|
||||
16 -> 31 : 0 | |".32 -> 63 : 0 | |
|
||||
16 -> 31 : 0 | |".32 -> 63 : 0 | |
|
||||
64 -> 127 : 1 | |
|
||||
128 -> 255 : 800 |**************************************|
|
||||
</code></pre>
|
||||
@@ -474,7 +474,7 @@ from bcc import BPF
|
||||
from time import sleep
|
||||
|
||||
# 加载BPF程序
|
||||
b = BPF(text="""
|
||||
b = BPF(text="""
|
||||
#include <uapi/linux/ptrace.h>
|
||||
#include <linux/blkdev.h>
|
||||
|
||||
@@ -485,10 +485,10 @@ int kprobe__blk_account_io_done(struct pt_regs *ctx, struct request *req)
|
||||
dist.increment(bpf_log2l(req->__data_len / 1024));
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
""")
|
||||
|
||||
# 头部
|
||||
print("跟踪中... 按Ctrl-C结束.")
|
||||
print("跟踪中... 按Ctrl-C结束.")
|
||||
|
||||
# 跟踪直到按下Ctrl-C
|
||||
try:
|
||||
@@ -497,7 +497,7 @@ except KeyboardInterrupt:
|
||||
print()
|
||||
|
||||
# 输出
|
||||
b["dist"].print_log2_hist("kbytes")
|
||||
b["dist"].print_log2_hist("kbytes")
|
||||
</code></pre>
|
||||
<p>之前课程的总结:</p>
|
||||
<ul>
|
||||
@@ -507,10 +507,10 @@ b["dist"].print_log2_hist("kbytes")
|
||||
</ul>
|
||||
<p>新知识:</p>
|
||||
<ol>
|
||||
<li><code>BPF_HISTOGRAM(dist)</code>: 定义了一个名为 "dist" 的BPF映射对象,它是一个直方图。</li>
|
||||
<li><code>BPF_HISTOGRAM(dist)</code>: 定义了一个名为 "dist" 的BPF映射对象,它是一个直方图。</li>
|
||||
<li><code>dist.increment()</code>: 默认情况下,将第一个参数提供的直方图桶索引加1。也可以作为第二个参数传递自定义的增量。</li>
|
||||
<li><code>bpf_log2l()</code>: 返回所提供值的对数值。这将成为我们直方图的索引,这样我们构建了一个以2为底的幂直方图。</li>
|
||||
<li><code>b["dist"].print_log2_hist("kbytes")</code>: 以2为底的幂形式打印 "dist" 直方图,列标题为 "kbytes"。这样只有桶计数从内核传输到用户空间,因此效率高。</li>
|
||||
<li><code>b["dist"].print_log2_hist("kbytes")</code>: 以2为底的幂形式打印 "dist" 直方图,列标题为 "kbytes"。这样只有桶计数从内核传输到用户空间,因此效率高。</li>
|
||||
</ol>
|
||||
<h3 id="lesson-10-disklatencypy-lesson-11-vfsreadlatpy"><a class="header" href="#lesson-10-disklatencypy-lesson-11-vfsreadlatpy">Lesson 10. disklatency.py”。#### Lesson 11. vfsreadlat.py</a></h3>
|
||||
<p>这个例子分为独立的Python和C文件。示例输出:</p>
|
||||
@@ -552,9 +552,9 @@ b["dist"].print_log2_hist("kbytes")
|
||||
<p>浏览 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/vfsreadlat.py">examples/tracing/vfsreadlat.py</a> 和 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/vfsreadlat.c">examples/tracing/vfsreadlat.c</a> 中的代码。</p>
|
||||
<p>学习的内容:</p>
|
||||
<ol>
|
||||
<li><code>b = BPF(src_file = "vfsreadlat.c")</code>: 从单独的源代码文件中读取 BPF C 程序。</li>
|
||||
<li><code>b.attach_kretprobe(event="vfs_read", fn_name="do_return")</code>: 将 BPF C 函数 <code>do_return()</code> 链接到内核函数 <code>vfs_read()</code> 的返回值上。这是一个 kretprobe:用于检测函数返回值,而不是函数的入口。</li>
|
||||
<li><code>b["dist"].clear()</code>: 清除直方图。</li>
|
||||
<li><code>b = BPF(src_file = "vfsreadlat.c")</code>: 从单独的源代码文件中读取 BPF C 程序。</li>
|
||||
<li><code>b.attach_kretprobe(event="vfs_read", fn_name="do_return")</code>: 将 BPF C 函数 <code>do_return()</code> 链接到内核函数 <code>vfs_read()</code> 的返回值上。这是一个 kretprobe:用于检测函数返回值,而不是函数的入口。</li>
|
||||
<li><code>b["dist"].clear()</code>: 清除直方图。</li>
|
||||
</ol>
|
||||
<h3 id="lesson-12-urandomreadpy"><a class="header" href="#lesson-12-urandomreadpy">Lesson 12. urandomread.py</a></h3>
|
||||
<p>当运行 <code>dd if=/dev/urandom of=/dev/null bs=8k count=5</code> 时进行跟踪:</p>
|
||||
@@ -568,20 +568,20 @@ TIME(s) COMM PID GOTBITS
|
||||
24652837.728888001 dd 24692 65536
|
||||
</code></pre>
|
||||
<p>哈!我意外地捕捉到了 smtp。代码在 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/urandomread.py">examples/tracing/urandomread.py</a> 中:</p>
|
||||
<pre><code class="language-Python">from __future__ import print_function".```python
|
||||
<pre><code class="language-Python">from __future__ import print_function".```python
|
||||
from bcc import BPF
|
||||
|
||||
# 加载BPF程序
|
||||
b = BPF(text="""
|
||||
b = BPF(text="""
|
||||
TRACEPOINT_PROBE(random, urandom_read) {
|
||||
// args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
|
||||
bpf_trace_printk("%d\\n", args->got_bits);
|
||||
bpf_trace_printk("%d\\n", args->got_bits);
|
||||
return 0;
|
||||
}
|
||||
""")
|
||||
""")
|
||||
|
||||
# header
|
||||
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "GOTBITS"))
|
||||
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "GOTBITS"))
|
||||
|
||||
# format output
|
||||
while 1:
|
||||
@@ -589,7 +589,7 @@ while 1:
|
||||
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
|
||||
except ValueError:
|
||||
continue
|
||||
print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
|
||||
print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
|
||||
</code></pre>
|
||||
<p>要学到的东西:</p>
|
||||
<ol>
|
||||
@@ -609,7 +609,7 @@ format:
|
||||
field:int pool_left; offset:12; size:4; signed:1;
|
||||
field:int input_left; offset:16; size:4; signed:1;
|
||||
|
||||
print fmt: "got_bits %d nonblocking_pool_entropy_left %d input_entropy_left %d", REC->got_bits, REC->pool_left, REC->input_left
|
||||
print fmt: "got_bits %d nonblocking_pool_entropy_left %d input_entropy_left %d", REC->got_bits, REC->pool_left, REC->input_left
|
||||
</code></pre>
|
||||
<p>在这种情况下,我们正在打印 <code>got_bits</code> 成员。</p>
|
||||
<h3 id="第13课-disksnooppy已修复"><a class="header" href="#第13课-disksnooppy已修复">第13课. disksnoop.py已修复</a></h3>
|
||||
@@ -619,31 +619,31 @@ print fmt: "got_bits %d nonblocking_pool_entropy_left %d input_entropy_left
|
||||
<pre><code class="language-sh"># strlen_count.py
|
||||
跟踪 strlen()... 按 Ctrl-C 结束。
|
||||
^C 数量 字符串
|
||||
1 " "
|
||||
1 "/bin/ls"
|
||||
1 "."
|
||||
1 "cpudist.py.1"
|
||||
1 ".bashrc"
|
||||
1 "ls --color=auto"
|
||||
1 "key_t"
|
||||
1 " "
|
||||
1 "/bin/ls"
|
||||
1 "."
|
||||
1 "cpudist.py.1"
|
||||
1 ".bashrc"
|
||||
1 "ls --color=auto"
|
||||
1 "key_t"
|
||||
[...]
|
||||
10 "a7:~# "
|
||||
10 "/root"
|
||||
12 "LC_ALL"
|
||||
12 "en_US.UTF-8"
|
||||
13 "en_US.UTF-8"
|
||||
20 "~"
|
||||
70 "#%^,~:-=?+/}"
|
||||
340 "\x01\x1b]0;root@bgregg-test: ~\x07\x02root@bgregg-test:~# "
|
||||
10 "a7:~# "
|
||||
10 "/root"
|
||||
12 "LC_ALL"
|
||||
12 "en_US.UTF-8"
|
||||
13 "en_US.UTF-8"
|
||||
20 "~"
|
||||
70 "#%^,~:-=?+/}"
|
||||
340 "\x01\x1b]0;root@bgregg-test: ~\x07\x02root@bgregg-test:~# "
|
||||
</code></pre>
|
||||
<p>这些是在跟踪时由此库函数处理的各种字符串以及它们的频率计数。例如,"LC_ALL" 被调用了12次。</p>
|
||||
<p>这些是在跟踪时由此库函数处理的各种字符串以及它们的频率计数。例如,"LC_ALL" 被调用了12次。</p>
|
||||
<p>代码在 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/strlen_count.py">examples/tracing/strlen_count.py</a> 中:</p>
|
||||
<pre><code class="language-Python">from __future__ import print_function
|
||||
from bcc import BPF
|
||||
from time import sleep
|
||||
|
||||
# 载入 BPF 程序
|
||||
b = BPF(text="""
|
||||
b = BPF(text="""
|
||||
#include <uapi/linux/ptrace.h>
|
||||
|
||||
struct key_t {
|
||||
@@ -666,11 +666,11 @@ int count(struct pt_regs *ctx) {
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
""")
|
||||
b.attach_uprobe(name="c", sym="strlen", fn_name="count")
|
||||
""")
|
||||
b.attach_uprobe(name="c", sym="strlen", fn_name="count")
|
||||
|
||||
# 头部
|
||||
print("跟踪 strlen()... 按 Ctrl-C 结束。")
|
||||
print("跟踪 strlen()... 按 Ctrl-C 结束。")
|
||||
|
||||
# 睡眠直到按下 Ctrl-C
|
||||
try:
|
||||
@@ -679,14 +679,14 @@ except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
# 打印输出
|
||||
print("%10s %s" % ("数量", "字符串"))
|
||||
counts = b.get_table("counts")
|
||||
print("%10s %s" % ("数量", "字符串"))
|
||||
counts = b.get_table("counts")
|
||||
for k, v in sorted(counts.items(), key=lambda counts: counts[1].value):
|
||||
print("%10d \"%s\"" % (v.value, k.c.encode('string-escape')))
|
||||
print("%10d \"%s\"" % (v.value, k.c.encode('string-escape')))
|
||||
</code></pre>
|
||||
<p>要学习的内容:1. <code>PT_REGS_PARM1(ctx)</code>: 这个参数会获取传递给 <code>strlen()</code> 的第一个参数,也就是字符串。</p>
|
||||
<ol>
|
||||
<li><code>b.attach_uprobe(name="c", sym="strlen", fn_name="count")</code>: 附加到库 "c"(如果这是主程序,则使用其路径名),对用户级函数 <code>strlen()</code> 进行插装,并在执行时调用我们的 C 函数 <code>count()</code>。</li>
|
||||
<li><code>b.attach_uprobe(name="c", sym="strlen", fn_name="count")</code>: 附加到库 "c"(如果这是主程序,则使用其路径名),对用户级函数 <code>strlen()</code> 进行插装,并在执行时调用我们的 C 函数 <code>count()</code>。</li>
|
||||
</ol>
|
||||
<h3 id="第15课nodejs_http_serverpy"><a class="header" href="#第15课nodejs_http_serverpy">第15课。nodejs_http_server.py</a></h3>
|
||||
<p>本程序会对用户静态定义的跟踪 (USDT) 探测点进行插装,这是内核跟踪点的用户级版本。示例输出:</p>
|
||||
@@ -702,27 +702,27 @@ from bcc import BPF, USDT
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("USAGE: nodejs_http_server PID")
|
||||
print("USAGE: nodejs_http_server PID")
|
||||
exit()
|
||||
pid = sys.argv[1]
|
||||
debug = 0
|
||||
|
||||
# load BPF program
|
||||
bpf_text = """
|
||||
bpf_text = """
|
||||
#include <uapi/linux/ptrace.h>
|
||||
int do_trace(struct pt_regs *ctx) {
|
||||
uint64_t addr;
|
||||
char path[128]={0};
|
||||
bpf_usdt_readarg(6, ctx, &addr);
|
||||
bpf_probe_read_user(&path, sizeof(path), (void *)addr);
|
||||
bpf_trace_printk("path:%s\\n", path);
|
||||
bpf_trace_printk("path:%s\\n", path);
|
||||
return 0;
|
||||
};
|
||||
"""
|
||||
"""
|
||||
|
||||
# enable USDT probe from given PID
|
||||
u = USDT(pid=int(pid))
|
||||
u.enable_probe(probe="http__server__request", fn_name="do_trace")
|
||||
u.enable_probe(probe="http__server__request", fn_name="do_trace")
|
||||
if debug:
|
||||
print(u.get_text())
|
||||
print(bpf_text)
|
||||
@@ -734,7 +734,7 @@ b = BPF(text=bpf_text, usdt_contexts=[u])
|
||||
<ol>
|
||||
<li><code>bpf_usdt_readarg(6, ctx, &addr)</code>: 从 USDT 探测点中读取参数 6 的地址到 <code>addr</code>。</li>
|
||||
<li><code>bpf_probe_read_user(&path, sizeof(path), (void *)addr)</code>: 现在字符串 <code>addr</code> 指向我们的 <code>path</code> 变量。</li>
|
||||
<li><code>u = USDT(pid=int(pid))</code>: 为给定的 PID 初始化 USDT 跟踪。1. <code>u.enable_probe(probe="http__server__request", fn_name="do_trace")</code>: 将我们的 <code>do_trace()</code> BPF C 函数附加到 Node.js 的 <code>http__server__request</code> USDT 探针。</li>
|
||||
<li><code>u = USDT(pid=int(pid))</code>: 为给定的 PID 初始化 USDT 跟踪。1. <code>u.enable_probe(probe="http__server__request", fn_name="do_trace")</code>: 将我们的 <code>do_trace()</code> BPF C 函数附加到 Node.js 的 <code>http__server__request</code> USDT 探针。</li>
|
||||
<li><code>b = BPF(text=bpf_text, usdt_contexts=[u])</code>: 需要将我们的 USDT 对象 <code>u</code> 传递给 BPF 对象的创建。</li>
|
||||
</ol>
|
||||
<h3 id="第16课-task_switchc"><a class="header" href="#第16课-task_switchc">第16课. task_switch.c</a></h3>
|
||||
@@ -770,15 +770,15 @@ BPF 对象的 <code>[]</code> 运算符允许访问程序中的每个 BPF_HASH
|
||||
<pre><code class="language-python">from bcc import BPF
|
||||
from time import sleep
|
||||
|
||||
b = BPF(src_file="task_switch.c")".```markdown
|
||||
b = BPF(src_file="task_switch.c")".```markdown
|
||||
```Chinese
|
||||
b.attach_kprobe(event="finish_task_switch", fn_name="count_sched")
|
||||
b.attach_kprobe(event="finish_task_switch", fn_name="count_sched")
|
||||
|
||||
# 生成多个调度事件
|
||||
for i in range(0, 100): sleep(0.01)
|
||||
|
||||
for k, v in b["stats"].items():
|
||||
print("task_switch[%5d->%5d]=%u" % (k.prev_pid, k.curr_pid, v.value))
|
||||
for k, v in b["stats"].items():
|
||||
print("task_switch[%5d->%5d]=%u" % (k.prev_pid, k.curr_pid, v.value))
|
||||
</code></pre>
|
||||
<p>这些程序可以在文件 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/task_switch.c">examples/tracing/task_switch.c</a> 和 <a href="https://github.com/iovisor/bcc/tree/master/examples/tracing/task_switch.py">examples/tracing/task_switch.py</a> 中找到。</p>
|
||||
<h3 id="第17课-进一步研究"><a class="header" href="#第17课-进一步研究">第17课. 进一步研究</a></h3>
|
||||
|
||||
@@ -179,14 +179,14 @@
|
||||
<h1 id="1-列出所有探针"><a class="header" href="#1-列出所有探针">1. 列出所有探针</a></h1>
|
||||
<pre><code>bpftrace -l 'tracepoint:syscalls:sys_enter_*'
|
||||
</code></pre>
|
||||
<p>"bpftrace -l" 列出所有探针,并且可以添加搜索项。</p>
|
||||
<p>"bpftrace -l" 列出所有探针,并且可以添加搜索项。</p>
|
||||
<ul>
|
||||
<li>探针是用于捕获事件数据的检测点。</li>
|
||||
<li>搜索词支持通配符,如<code>*</code>和<code>?</code>。</li>
|
||||
<li>"bpftrace -l" 也可以通过管道传递给grep,进行完整的正则表达式搜索。</li>
|
||||
<li>"bpftrace -l" 也可以通过管道传递给grep,进行完整的正则表达式搜索。</li>
|
||||
</ul>
|
||||
<h1 id="2-hello-world"><a class="header" href="#2-hello-world">2. Hello World</a></h1>
|
||||
<pre><code># bpftrace -e 'BEGIN { printf("hello world\n"); }'
|
||||
<pre><code># bpftrace -e 'BEGIN { printf("hello world\n"); }'
|
||||
Attaching 1 probe...
|
||||
hello world
|
||||
^C
|
||||
@@ -197,7 +197,7 @@ hello world
|
||||
<li>探针可以关联动作,把动作放到{}中。这个例子中,探针被触发时会调用printf()。</li>
|
||||
</ul>
|
||||
<h1 id="3-文件打开"><a class="header" href="#3-文件打开">3. 文件打开</a></h1>
|
||||
<pre><code># bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args.filename)); }'
|
||||
<pre><code># bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args.filename)); }'
|
||||
Attaching 1 probe...
|
||||
snmp-pass /proc/cpuinfo
|
||||
snmp-pass /proc/stat
|
||||
@@ -247,9 +247,9 @@ Attaching 1 probe...
|
||||
</code></pre>
|
||||
<p>这里统计进程号为18644的进程执行内核函数sys_read()的返回值,并打印出直方图。</p>
|
||||
<ul>
|
||||
<li>/.../: 这里设置一个过滤条件(条件判断),满足该过滤条件时才执行{}里面的动作。在这个例子中意思是只追踪进程号为18644的进程。过滤条件表达式也支持布尔运算,如("&&", "||")。</li>
|
||||
<li>/.../: 这里设置一个过滤条件(条件判断),满足该过滤条件时才执行{}里面的动作。在这个例子中意思是只追踪进程号为18644的进程。过滤条件表达式也支持布尔运算,如("&&", "||")。</li>
|
||||
<li>ret: 表示函数的返回值。对于sys_read(),它可能是-1(错误)或者成功读取的字节数。</li>
|
||||
<li>@: 类似于上节的map,但是这里没有key,即[]。该map的名称"bytes"会出现在输出中。</li>
|
||||
<li>@: 类似于上节的map,但是这里没有key,即[]。该map的名称"bytes"会出现在输出中。</li>
|
||||
<li>hist(): 一个map函数,用来描述直方图的参数。输出行以2次方的间隔开始,如<code>[128, 256)</code>表示值大于等于128且小于256。后面跟着位于该区间的参数个数统计,最后是ascii码表示的直方图。该图可以用来研究它的模式分布。</li>
|
||||
<li>其它的map函数还有lhist(线性直方图),count(),sum(),avg(),min()和max()。</li>
|
||||
</ul>
|
||||
@@ -274,7 +274,7 @@ Attaching 1 probe...
|
||||
</code></pre>
|
||||
<p>使用内核动态跟踪技术显示read()返回字节数的直方图。</p>
|
||||
<ul>
|
||||
<li><code>kretprobe:vfs_read</code>: 这是kretprobe类型(动态跟踪内核函数返回值)的探针,跟踪<code>vfs_read</code>内核函数。此外还有kprobe类型的探针(在下一节介绍)用于跟踪内核函数的调用。它们是功能强大的探针类型,让我们可以跟踪成千上万的内核函数。然而它们是"不稳定"的探针类型:由于它们可以跟踪任意内核函数,对于不同的内核版本,kprobe和kretprobe不一定能够正常工作。因为内核函数名,参数,返回值和作用等可能会变化。此外,由于它们用来跟踪底层内核的,你需要浏览内核源代码,理解这些探针的参数和返回值的意义。</li>
|
||||
<li><code>kretprobe:vfs_read</code>: 这是kretprobe类型(动态跟踪内核函数返回值)的探针,跟踪<code>vfs_read</code>内核函数。此外还有kprobe类型的探针(在下一节介绍)用于跟踪内核函数的调用。它们是功能强大的探针类型,让我们可以跟踪成千上万的内核函数。然而它们是"不稳定"的探针类型:由于它们可以跟踪任意内核函数,对于不同的内核版本,kprobe和kretprobe不一定能够正常工作。因为内核函数名,参数,返回值和作用等可能会变化。此外,由于它们用来跟踪底层内核的,你需要浏览内核源代码,理解这些探针的参数和返回值的意义。</li>
|
||||
<li>lhist(): 线性直方图函数:参数分别是value,最小值,最大值,步进值。第一个参数(<code>retval</code>)表示系统调用sys_read()返回值:即成功读取的字节数。</li>
|
||||
</ul>
|
||||
<h1 id="7-read调用的时间"><a class="header" href="#7-read调用的时间">7. read()调用的时间</a></h1>
|
||||
@@ -434,7 +434,7 @@ Attaching 1 probe...
|
||||
|
||||
kprobe:vfs_open
|
||||
{
|
||||
printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name));
|
||||
printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name));
|
||||
}
|
||||
|
||||
# bpftrace path.bt
|
||||
|
||||
1404
print.html
1404
print.html
File diff suppressed because it is too large
Load Diff
@@ -316,7 +316,7 @@ window.search = window.search || {};
|
||||
|
||||
// Eventhandler for keyevents on `document`
|
||||
function globalKeyHandler(e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; }
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; }
|
||||
|
||||
if (e.keyCode === ESCAPE_KEYCODE) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user