This commit is contained in:
yunwei37
2023-07-21 16:25:37 +00:00
parent b8bf25ab5a
commit c36c550e16
4 changed files with 76 additions and 80 deletions

View File

@@ -171,21 +171,21 @@
<p>本文是 eBPF 入门开发实践教程的第二篇,在 eBPF 中使用 kprobe 捕获 unlink 系统调用。本文会先讲解关于 kprobes 的基本概念和技术背景,然后介绍如何在 eBPF 中使用 kprobe 捕获 unlink 系统调用。</p>
<h2 id="kprobes-技术背景"><a class="header" href="#kprobes-技术背景">kprobes 技术背景</a></h2>
<p>开发人员在内核或者模块的调试过程中,往往会需要要知道其中的一些函数有无被调用、何时被调用、执行是否正确以及函数的入参和返回值是什么等等。比较简单的做法是在内核代码对应的函数中添加日志打印信息,但这种方式往往需要重新编译内核或模块,重新启动设备之类的,操作较为复杂甚至可能会破坏原有的代码执行过程。</p>
<p>而利用kprobes技术用户可以定义自己的回调函数然后在内核或者模块中几乎所有的函数中有些函数是不可探测的例如kprobes自身的相关实现函数后文会有详细说明动态插入探测点当内核执行流程执行到指定的探测函数时会调用该回调函数用户即可收集所需的信息了同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息不再需要继续探测则同样可以动态地移除探测点。因此kprobes技术具有对内核执行流程影响小和操作方便的优点。</p>
<p>kprobes技术包括的3种探测手段分别时kprobe、jprobekretprobe。首先kprobe是最基本的探测方式是实现后两种的基础它可以在任意的位置放置探测点就连函数内部的某条指令处也可以它提供了探测点的调用前、调用后和内存访问出错3种回调方式分别是pre_handler、post_handler和fault_handler其中pre_handler函数将在被探测指令被执行前回调post_handler会在被探测指令执行完毕后回调注意不是被探测函数fault_handler会在内存访问出错时被调用jprobe基于kprobe实现它用于获取被探测函数的入参值最后kretprobe从名字中就可以看出其用途了它同样基于kprobe实现用于获取被探测函数的返回值。</p>
<p>kprobes的技术原理并不仅仅包含软件的实现方案它也需要硬件架构提供支持。其中涉及硬件架构相关的是CPU的异常处理和单步调试技术前者用于让程序的执行流程陷入到用户注册的回调函数中去而后者则用于单步执行被探测点指令因此并不是所有的架构均支持目前kprobes技术已经支持多种架构包括i386、x86_64、ppc64、ia64、sparc64、arm、ppcmips有些架构实现可能并不完全具体可参考内核的Documentation/kprobes.txt</p>
<p>kprobes的特点与使用限制</p>
<p>而利用 kprobes 技术用户可以定义自己的回调函数然后在内核或者模块中几乎所有的函数中有些函数是不可探测的例如kprobes自身的相关实现函数后文会有详细说明动态插入探测点,当内核执行流程执行到指定的探测函数时,会调用该回调函数,用户即可收集所需的信息了,同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息,不再需要继续探测,则同样可以动态地移除探测点。因此 kprobes 技术具有对内核执行流程影响小和操作方便的优点。</p>
<p>kprobes 技术包括的3种探测手段分别时 kprobe、jprobekretprobe。首先 kprobe 是最基本的探测方式是实现后两种的基础它可以在任意的位置放置探测点就连函数内部的某条指令处也可以它提供了探测点的调用前、调用后和内存访问出错3种回调方式分别是 <code>pre_handler</code><code>post_handler</code><code>fault_handler</code>,其中 <code>pre_handler</code> 函数将在被探测指令被执行前回调,<code>post_handler</code> 会在被探测指令执行完毕后回调(注意不是被探测函数),<code>fault_handler</code> 会在内存访问出错时被调用jprobe 基于 kprobe 实现,它用于获取被探测函数的入参值;最后 kretprobe 从名字中就可以看出其用途了,它同样基于 kprobe 实现,用于获取被探测函数的返回值。</p>
<p>kprobes 的技术原理并不仅仅包含软件的实现方案,它也需要硬件架构提供支持。其中涉及硬件架构相关的是 CPU 的异常处理和单步调试技术,前者用于让程序的执行流程陷入到用户注册的回调函数中去,而后者则用于单步执行被探测点指令,因此并不是所有的架构均支持 kprobes。目前 kprobes 技术已经支持多种架构,包括 i386、x86_64、ppc64、ia64、sparc64、arm、ppcmips有些架构实现可能并不完全具体可参考内核的 Documentation/kprobes.txt</p>
<p>kprobes 的特点与使用限制:</p>
<ol>
<li>kprobes允许在同一个被探测位置注册多个kprobe但是目前jprobe却不可以同时也不允许以其他的jprobe回调函数和kprobe的post_handler回调函数作为被探测点。</li>
<li>一般情况下可以探测内核中的任何函数包括中断处理函数。不过在kernel/kprobes.carch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数是不允许被探测的另外还有do_page_fault和notifier_call_chain</li>
<li>如果以一个内联函数为探测点则kprobes可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数因此可能无法达到用户预期的探测效果</li>
<li>一个探测点的回调函数可能会修改被探测函数运行上下文,例如通过修改内核的数据结构或者保存与struct pt_regs结构体中的触发探测器之前寄存器信息。因此kprobes可以被用来安装bug修复代码或者注入故障测试代码</li>
<li>kprobes会避免在处理探测点函数时再次调用另一个探测点的回调函数例如在printk()函数上注册了探测点,在它的回调函数中可能再次调用printk函数此时将不再触发printk探测点的回调,仅仅增加了kprobe结构体中nmissed字段的数值;</li>
<li>在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存</li>
<li>kprobes回调函数的运行期间是关闭内核抢占的同时也可能在关闭中断的情况下执行具体要视CPU架构而定。因此不论在何种情况下在回调函数中不要调用会放弃CPU的函数如信号量、mutex锁等</li>
<li>kretprobe通过替换返回地址为预定义的trampoline的地址来实现因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址</li>
<li>如果一个函数的调用次数和返回次数不相等则在类似这样的函数上注册kretprobe将可能不会达到预期的效果例如do_exit()函数会存在问题而do_execve()函数和do_fork()函数不会;</li>
<li>如果当在进入和退出一个函数时CPU运行在非当前任务所有的栈上那么往该函数上注册kretprobe可能会导致不可预料的后果因此kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe将直接返回-EINVAL</li>
<li>kprobes 允许在同一个被探测位置注册多个 kprobe但是目前 jprobe 却不可以;同时也不允许以其他的 jprobe 回调函数和 kprobe<code>post_handler</code> 回调函数作为被探测点。</li>
<li>一般情况下,可以探测内核中的任何函数,包括中断处理函数。不过在 kernel/kprobes.carch/*/kernel/kprobes.c 程序中用于实现 kprobes 自身的函数是不允许被探测的,另外还有<code>do_page_fault</code><code>notifier_call_chain</code></li>
<li>如果以一个内联函数为探测点,则 kprobes 可能无法保证对该函数的所有实例都注册探测点。由于 gcc 可能会自动将某些函数优化为内联函数,因此可能无法达到用户预期的探测效果;</li>
<li>一个探测点的回调函数可能会修改被探测函数运行上下文,例如通过修改内核的数据结构或者保存与<code>struct pt_regs</code>结构体中的触发探测器之前寄存器信息。因此 kprobes 可以被用来安装 bug 修复代码或者注入故障测试代码;</li>
<li>kprobes 会避免在处理探测点函数时再次调用另一个探测点的回调函数,例如在<code>printk()</code>函数上注册了探测点,在它的回调函数中可能再次调用<code>printk</code>函数,此时将不再触发<code>printk</code>探测点的回调,仅仅增加了<code>kprobe</code>结构体中<code>nmissed</code>字段的数值;</li>
<li> kprobes 的注册和注销过程中不会使用 mutex 锁和动态的申请内存;</li>
<li>kprobes 回调函数的运行期间是关闭内核抢占的同时也可能在关闭中断的情况下执行具体要视CPU架构而定。因此不论在何种情况下在回调函数中不要调用会放弃 CPU 的函数如信号量、mutex 锁等);</li>
<li>kretprobe 通过替换返回地址为预定义的 trampoline 的地址来实现,因此栈回溯和 gcc 内嵌函数<code>__builtin_return_address()</code>调用将返回 trampoline 的地址而不是真正的被探测函数的返回地址;</li>
<li>如果一个函数的调用次数和返回次数不相等,则在类似这样的函数上注册 kretprobe 将可能不会达到预期的效果,例如<code>do_exit()</code>函数会存在问题,而<code>do_execve()</code>函数和<code>do_fork()</code>函数不会;</li>
<li>当在进入和退出一个函数时,如果 CPU 运行在非当前任务所有的栈上,那么往该函数上注册 kretprobe 可能会导致不可预料的后果因此kprobes 不支持在 X86_64 的结构下为<code>__switch_to()</code>函数注册 kretprobe将直接返回<code>-EINVAL</code></li>
</ol>
<h2 id="kprobe-示例"><a class="header" href="#kprobe-示例">kprobe 示例</a></h2>
<p>完整代码如下:</p>
@@ -199,26 +199,26 @@ char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
SEC(&quot;kprobe/do_unlinkat&quot;)
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
{
pid_t pid;
const char *filename;
pid_t pid;
const char *filename;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
}
SEC(&quot;kretprobe/do_unlinkat&quot;)
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
{
pid_t pid;
pid_t pid;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
}
</code></pre>
<p>这段代码是一个简单的 eBPF 程序,用于监测和捕获在 Linux 内核中执行的 unlink 系统调用。unlink 系统调用的功能是删除一个文件,这个 eBPF 程序通过使用 kprobe内核探针 do_unlinkat 函数的入口和退出处放置钩子,实现对该系统调用的跟踪。</p>
<p>这段代码是一个简单的 eBPF 程序,用于监测和捕获在 Linux 内核中执行的 unlink 系统调用。unlink 系统调用的功能是删除一个文件,这个 eBPF 程序通过使用 kprobe内核探针<code>do_unlinkat</code>函数的入口和退出处放置钩子,实现对该系统调用的跟踪。</p>
<p>首先,我们导入必要的头文件,如 vmlinux.hbpf_helpers.hbpf_tracing.h 和 bpf_core_read.h。接着我们定义许可证以允许程序在内核中运行。</p>
<pre><code class="language-c">#include &quot;vmlinux.h&quot;
#include &lt;bpf/bpf_helpers.h&gt;
@@ -226,31 +226,29 @@ int BPF_KRETPROBE(do_unlinkat_exit, long ret)
#include &lt;bpf/bpf_core_read.h&gt;
char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
</code></pre>
<p>接下来,我们定义一个名为 BPF_KPROBE(do_unlinkat) 的 kprobe当进入 do_unlinkat 函数时它会被触发。该函数接受两个参数dfd文件描述符和 name(文件名结构体指针)。在这个 kprobe 中,我们获取当前进程的 PID进程标识符然后读取文件名。最后我们使用 bpf_printk 函数在内核日志中打印 PID 和文件名。</p>
<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(&quot;kprobe/do_unlinkat&quot;)
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
{
pid_t pid;
const char *filename;
pid_t pid;
const char *filename;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
}
</code></pre>
<p>接下来,我们定义一个名为 BPF_KRETPROBE(do_unlinkat_exit) 的 kretprobe当从 do_unlinkat 函数退出时,它会被触发。这个 kretprobe 的目的是捕获函数的返回值ret。我们再次获取当前进程的 PID并使用 bpf_printk 函数在内核日志中打印 PID 和返回值。</p>
<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(&quot;kretprobe/do_unlinkat&quot;)
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
{
pid_t pid;
pid_t pid;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
}
</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 运行时。</p>

View File

@@ -445,21 +445,21 @@ Runing eBPF program...
<p>本文是 eBPF 入门开发实践教程的第二篇,在 eBPF 中使用 kprobe 捕获 unlink 系统调用。本文会先讲解关于 kprobes 的基本概念和技术背景,然后介绍如何在 eBPF 中使用 kprobe 捕获 unlink 系统调用。</p>
<h2 id="kprobes-技术背景"><a class="header" href="#kprobes-技术背景">kprobes 技术背景</a></h2>
<p>开发人员在内核或者模块的调试过程中,往往会需要要知道其中的一些函数有无被调用、何时被调用、执行是否正确以及函数的入参和返回值是什么等等。比较简单的做法是在内核代码对应的函数中添加日志打印信息,但这种方式往往需要重新编译内核或模块,重新启动设备之类的,操作较为复杂甚至可能会破坏原有的代码执行过程。</p>
<p>而利用kprobes技术用户可以定义自己的回调函数然后在内核或者模块中几乎所有的函数中有些函数是不可探测的例如kprobes自身的相关实现函数后文会有详细说明动态插入探测点当内核执行流程执行到指定的探测函数时会调用该回调函数用户即可收集所需的信息了同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息不再需要继续探测则同样可以动态地移除探测点。因此kprobes技术具有对内核执行流程影响小和操作方便的优点。</p>
<p>kprobes技术包括的3种探测手段分别时kprobe、jprobekretprobe。首先kprobe是最基本的探测方式是实现后两种的基础它可以在任意的位置放置探测点就连函数内部的某条指令处也可以它提供了探测点的调用前、调用后和内存访问出错3种回调方式分别是pre_handler、post_handler和fault_handler其中pre_handler函数将在被探测指令被执行前回调post_handler会在被探测指令执行完毕后回调注意不是被探测函数fault_handler会在内存访问出错时被调用jprobe基于kprobe实现它用于获取被探测函数的入参值最后kretprobe从名字中就可以看出其用途了它同样基于kprobe实现用于获取被探测函数的返回值。</p>
<p>kprobes的技术原理并不仅仅包含软件的实现方案它也需要硬件架构提供支持。其中涉及硬件架构相关的是CPU的异常处理和单步调试技术前者用于让程序的执行流程陷入到用户注册的回调函数中去而后者则用于单步执行被探测点指令因此并不是所有的架构均支持目前kprobes技术已经支持多种架构包括i386、x86_64、ppc64、ia64、sparc64、arm、ppcmips有些架构实现可能并不完全具体可参考内核的Documentation/kprobes.txt</p>
<p>kprobes的特点与使用限制</p>
<p>而利用 kprobes 技术用户可以定义自己的回调函数然后在内核或者模块中几乎所有的函数中有些函数是不可探测的例如kprobes自身的相关实现函数后文会有详细说明动态插入探测点,当内核执行流程执行到指定的探测函数时,会调用该回调函数,用户即可收集所需的信息了,同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息,不再需要继续探测,则同样可以动态地移除探测点。因此 kprobes 技术具有对内核执行流程影响小和操作方便的优点。</p>
<p>kprobes 技术包括的3种探测手段分别时 kprobe、jprobekretprobe。首先 kprobe 是最基本的探测方式是实现后两种的基础它可以在任意的位置放置探测点就连函数内部的某条指令处也可以它提供了探测点的调用前、调用后和内存访问出错3种回调方式分别是 <code>pre_handler</code><code>post_handler</code><code>fault_handler</code>,其中 <code>pre_handler</code> 函数将在被探测指令被执行前回调,<code>post_handler</code> 会在被探测指令执行完毕后回调(注意不是被探测函数),<code>fault_handler</code> 会在内存访问出错时被调用jprobe 基于 kprobe 实现,它用于获取被探测函数的入参值;最后 kretprobe 从名字中就可以看出其用途了,它同样基于 kprobe 实现,用于获取被探测函数的返回值。</p>
<p>kprobes 的技术原理并不仅仅包含软件的实现方案,它也需要硬件架构提供支持。其中涉及硬件架构相关的是 CPU 的异常处理和单步调试技术,前者用于让程序的执行流程陷入到用户注册的回调函数中去,而后者则用于单步执行被探测点指令,因此并不是所有的架构均支持 kprobes。目前 kprobes 技术已经支持多种架构,包括 i386、x86_64、ppc64、ia64、sparc64、arm、ppcmips有些架构实现可能并不完全具体可参考内核的 Documentation/kprobes.txt</p>
<p>kprobes 的特点与使用限制:</p>
<ol>
<li>kprobes允许在同一个被探测位置注册多个kprobe但是目前jprobe却不可以同时也不允许以其他的jprobe回调函数和kprobe的post_handler回调函数作为被探测点。</li>
<li>一般情况下可以探测内核中的任何函数包括中断处理函数。不过在kernel/kprobes.carch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数是不允许被探测的另外还有do_page_fault和notifier_call_chain</li>
<li>如果以一个内联函数为探测点则kprobes可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数因此可能无法达到用户预期的探测效果</li>
<li>一个探测点的回调函数可能会修改被探测函数运行上下文,例如通过修改内核的数据结构或者保存与struct pt_regs结构体中的触发探测器之前寄存器信息。因此kprobes可以被用来安装bug修复代码或者注入故障测试代码</li>
<li>kprobes会避免在处理探测点函数时再次调用另一个探测点的回调函数例如在printk()函数上注册了探测点,在它的回调函数中可能再次调用printk函数此时将不再触发printk探测点的回调,仅仅增加了kprobe结构体中nmissed字段的数值;</li>
<li>在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存</li>
<li>kprobes回调函数的运行期间是关闭内核抢占的同时也可能在关闭中断的情况下执行具体要视CPU架构而定。因此不论在何种情况下在回调函数中不要调用会放弃CPU的函数如信号量、mutex锁等</li>
<li>kretprobe通过替换返回地址为预定义的trampoline的地址来实现因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址</li>
<li>如果一个函数的调用次数和返回次数不相等则在类似这样的函数上注册kretprobe将可能不会达到预期的效果例如do_exit()函数会存在问题而do_execve()函数和do_fork()函数不会;</li>
<li>如果当在进入和退出一个函数时CPU运行在非当前任务所有的栈上那么往该函数上注册kretprobe可能会导致不可预料的后果因此kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe将直接返回-EINVAL</li>
<li>kprobes 允许在同一个被探测位置注册多个 kprobe但是目前 jprobe 却不可以;同时也不允许以其他的 jprobe 回调函数和 kprobe<code>post_handler</code> 回调函数作为被探测点。</li>
<li>一般情况下,可以探测内核中的任何函数,包括中断处理函数。不过在 kernel/kprobes.carch/*/kernel/kprobes.c 程序中用于实现 kprobes 自身的函数是不允许被探测的,另外还有<code>do_page_fault</code><code>notifier_call_chain</code></li>
<li>如果以一个内联函数为探测点,则 kprobes 可能无法保证对该函数的所有实例都注册探测点。由于 gcc 可能会自动将某些函数优化为内联函数,因此可能无法达到用户预期的探测效果;</li>
<li>一个探测点的回调函数可能会修改被探测函数运行上下文,例如通过修改内核的数据结构或者保存与<code>struct pt_regs</code>结构体中的触发探测器之前寄存器信息。因此 kprobes 可以被用来安装 bug 修复代码或者注入故障测试代码;</li>
<li>kprobes 会避免在处理探测点函数时再次调用另一个探测点的回调函数,例如在<code>printk()</code>函数上注册了探测点,在它的回调函数中可能再次调用<code>printk</code>函数,此时将不再触发<code>printk</code>探测点的回调,仅仅增加了<code>kprobe</code>结构体中<code>nmissed</code>字段的数值;</li>
<li> kprobes 的注册和注销过程中不会使用 mutex 锁和动态的申请内存;</li>
<li>kprobes 回调函数的运行期间是关闭内核抢占的同时也可能在关闭中断的情况下执行具体要视CPU架构而定。因此不论在何种情况下在回调函数中不要调用会放弃 CPU 的函数如信号量、mutex 锁等);</li>
<li>kretprobe 通过替换返回地址为预定义的 trampoline 的地址来实现,因此栈回溯和 gcc 内嵌函数<code>__builtin_return_address()</code>调用将返回 trampoline 的地址而不是真正的被探测函数的返回地址;</li>
<li>如果一个函数的调用次数和返回次数不相等,则在类似这样的函数上注册 kretprobe 将可能不会达到预期的效果,例如<code>do_exit()</code>函数会存在问题,而<code>do_execve()</code>函数和<code>do_fork()</code>函数不会;</li>
<li>当在进入和退出一个函数时,如果 CPU 运行在非当前任务所有的栈上,那么往该函数上注册 kretprobe 可能会导致不可预料的后果因此kprobes 不支持在 X86_64 的结构下为<code>__switch_to()</code>函数注册 kretprobe将直接返回<code>-EINVAL</code></li>
</ol>
<h2 id="kprobe-示例"><a class="header" href="#kprobe-示例">kprobe 示例</a></h2>
<p>完整代码如下:</p>
@@ -473,26 +473,26 @@ char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
SEC(&quot;kprobe/do_unlinkat&quot;)
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
{
pid_t pid;
const char *filename;
pid_t pid;
const char *filename;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
}
SEC(&quot;kretprobe/do_unlinkat&quot;)
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
{
pid_t pid;
pid_t pid;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
}
</code></pre>
<p>这段代码是一个简单的 eBPF 程序,用于监测和捕获在 Linux 内核中执行的 unlink 系统调用。unlink 系统调用的功能是删除一个文件,这个 eBPF 程序通过使用 kprobe内核探针 do_unlinkat 函数的入口和退出处放置钩子,实现对该系统调用的跟踪。</p>
<p>这段代码是一个简单的 eBPF 程序,用于监测和捕获在 Linux 内核中执行的 unlink 系统调用。unlink 系统调用的功能是删除一个文件,这个 eBPF 程序通过使用 kprobe内核探针<code>do_unlinkat</code>函数的入口和退出处放置钩子,实现对该系统调用的跟踪。</p>
<p>首先,我们导入必要的头文件,如 vmlinux.hbpf_helpers.hbpf_tracing.h 和 bpf_core_read.h。接着我们定义许可证以允许程序在内核中运行。</p>
<pre><code class="language-c">#include &quot;vmlinux.h&quot;
#include &lt;bpf/bpf_helpers.h&gt;
@@ -500,31 +500,29 @@ int BPF_KRETPROBE(do_unlinkat_exit, long ret)
#include &lt;bpf/bpf_core_read.h&gt;
char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
</code></pre>
<p>接下来,我们定义一个名为 BPF_KPROBE(do_unlinkat) 的 kprobe当进入 do_unlinkat 函数时它会被触发。该函数接受两个参数dfd文件描述符和 name(文件名结构体指针)。在这个 kprobe 中,我们获取当前进程的 PID进程标识符然后读取文件名。最后我们使用 bpf_printk 函数在内核日志中打印 PID 和文件名。</p>
<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(&quot;kprobe/do_unlinkat&quot;)
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
{
pid_t pid;
const char *filename;
pid_t pid;
const char *filename;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
filename = BPF_CORE_READ(name, name);
bpf_printk(&quot;KPROBE ENTRY pid = %d, filename = %s\n&quot;, pid, filename);
return 0;
}
</code></pre>
<p>接下来,我们定义一个名为 BPF_KRETPROBE(do_unlinkat_exit) 的 kretprobe当从 do_unlinkat 函数退出时,它会被触发。这个 kretprobe 的目的是捕获函数的返回值ret。我们再次获取当前进程的 PID并使用 bpf_printk 函数在内核日志中打印 PID 和返回值。</p>
<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(&quot;kretprobe/do_unlinkat&quot;)
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
{
pid_t pid;
pid_t pid;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
pid = bpf_get_current_pid_tgid() &gt;&gt; 32;
bpf_printk(&quot;KPROBE EXIT: pid = %d, ret = %ld\n&quot;, pid, ret);
return 0;
}
</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 运行时。</p>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long