type
Post
status
Published
slug
2023/01/06/bpf-example-program-kprobes-and-kretprobes
summary
tags
Python
Linux
BPF
eBPF
category
BPF
icon
password
Property
Jan 14, 2023 06:03 AM
created days
英文词典中单词 probe 有一个定义是:
一种无人探索航天器,旨在传递环境相关的信息。
当我们谈论跟踪探针时,我们可以使用非常相似的定义。
💡
跟踪探针是探索程序,旨在传递程序执行时环境的相关信息。
内核探针几乎可以在任何内核指令上设置动态标志或中断,并且系统损耗最小。当内核到达这些标志时,附加到探针的代码将被执行,之后内核将恢复正常模式。内核探针可以提供系统中发生事件的信息,例如,系统中打开的文件和正在执行的二进制文件。需要注意一点,内核探针没有稳定的应用程序二进制接口 (ABI),这意味着它们可能会随着内核版本的演进而更改。如果将相同的探针附加到两个不同内核版本的系统,同样的程序代码也可能会无法正常工作。
内核探针分为两类:kprobes 和 kretprobes。两者的使用取决于你要将 BPF程序插入指令执行周期的哪个阶段。本节将指导你使用它们,将 BPF 程序附加到探针和从内核中提取信息。

1 kprobes

kprobes 允许在执行任何内核指令之前插人 BPF 程序。你需要知道插入点的函数签名,前面已提到内核探针不是稳定的 ABI,所以在不同的内核版本中运行相同程序设置探针时需要谨慎。当内核执行到设置探针的指令时,它将从代码执行处开始运行 BPF 程序,在 BPF 程序执行完成后将返回至插人 BPF程序处继续执行。
我们将编写一个打印运行二进制文件名的 BPF 程序,用来演示如何使用 kprobes。在示例中,我们将使用 Python 语言作为 BCC 工具的前端,你也可以使用任何其他的 BPF 工具来编写。

1.1 example.py

from bcc import BPF bpf_source = """ #include <uapi/linux/ptrace.h> int do_sys_execve(struct pt_regs *ctx) { // 1 char comm[16]; bpf_get_current_comm(&comm, sizeof(comm)); bpf_trace_printk("executing program: %s\\n", comm); return 0; } """ bpf = BPF(text=bpf_source) # 2 execve_function = bpf.get_syscall_fnname("execve") # 3 bpf.attach_kprobe(event=execve_function, fn_name="do_sys_execve") # 4 bpf.trace_print()
  1. BPF 程序调用帮助函数 bpf_get_current_comm 获得当前内核正在运行的命令名,并将它保存在 comm 变量中。因为内核对命令名有 16个字符的限制,所以我们将其定义为固定长度的数组。获得命令名后,打印至调试跟踪,运行该 Python 脚本将在控制台看到 BPF 获得的所有命令名
  1. 加载BPF程序到内核中
  1. 将 BPF 程序与 execve 系统调用关联。该系统调用的名称在不同的内核版本中是不同的,BCC 提供了获得该系统调用名称的功能,你无须记住正在运行的内核版本下该系统调用的名称。
  1. 输出跟踪日志,你可以查看该程序跟踪的所有命令名。

1.2 运行结果展示

notion image

2 kretprobes

kretprobes 是在内核指令有返回值时插入 BPF 程序。通常,我们会在一个 BPF 程序中同时使用 kprobeskretprobes,以便获得对内核指令的全面了解。我们将使用与前面类似的示例来演示 kretprobes 如何工作:

2.1 example.py

from bcc import BPF bpf_source = """ #include <uapi/linux/ptrace.h> int ret_sys_execve(struct pt_regs *ctx) { // 1 int return_value; char comm[16]; bpf_get_current_comm(&comm, sizeof(comm)); return_value = PT_REGS_RC(ctx); bpf_trace_printk("program: %s, return: %d\\n", comm, return_value); return 0; } """ bpf = BPF(text=bpf_source) # 2 execve_function = bpf.get_syscall_fnname("execve") bpf.attach_kretprobe(event=execve_function, fn_name="ret_sys_execve") # 3 bpf.trace_print()
  1. 定义实现 BPF 程序的函数。内核将在 execve 系统调用结束后立即执行它。宏 PT_REGS_RC 用来从这个特定上下文中读取 BPF 寄存器的返回值。我们还使用 bpf_trace_print 在调试日志中打印命令及其返回值。
  1. 初始化 BPF 程序并将它加载到内核中
  1. 这里附加函数为 attach_kretprobe

2.2 运行结果展示

notion image

3 什么是上下文参数?

你可能已经注意到,两个 BPF 程序被附加函数的第一个参数是相同的参教标识为 ctx。该参数称为上下文,提供了访问内核正在处理的信息这个上下文依赖于你正在运行的 BPF 程序的类型。CPU 将保存内核正在执行任务的不同信息,所以该结构还取决于系统架构。ARM处理器将包含与X64 处理器不同的一组寄存器。我们不必担心如何访问这些寄存器内核提供相应的宏访问这些寄存器,如 PT_REGS_RC
 
 
欢迎加入喵星计算机技术研究院,原创技术文章第一时间推送。
notion image
 
BPF 学习系列之 - 追踪点BPF 学习系列之 - 用户空间探针 - uprobes 与 uretprobes

  • Waline
  • Utterance