libbpf-bootstrap开发指南:用户态探针 - uprobe

目录

代码分析

BPF 程序分析

功能分析

头文件引入

SEC("uprobe//proc/self/exe:uprobed_sub")

BPF_KPROBE&BPF_KRETPROBE

用户态程序分析

功能说明

/proc/self/maps 文件

执行效果


代码分析

BPF 程序分析
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2020 Facebook */
#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

SEC("uprobe")
int BPF_KPROBE(uprobe_add, int a, int b)
{
	bpf_printk("uprobed_add ENTRY: a = %d, b = %d", a, b);
	return 0;
}

SEC("uretprobe")
int BPF_KRETPROBE(uretprobe_add, int ret)
{
	bpf_printk("uprobed_add EXIT: return = %d", ret);
	return 0;
}

SEC("uprobe//proc/self/exe:uprobed_sub")
int BPF_KPROBE(uprobe_sub, int a, int b)
{
	bpf_printk("uprobed_sub ENTRY: a = %d, b = %d", a, b);
	return 0;
}

SEC("uretprobe//proc/self/exe:uprobed_sub")
int BPF_KRETPROBE(uretprobe_sub, int ret)
{
	bpf_printk("uprobed_sub EXIT: return = %d", ret);
	return 0;
}
功能分析

主要定义了一些 uprobe 和 uretprobe 函数,用于在用户空间程序的特定函数进入和返回时进行跟踪。

头文件引入
  1. #include <linux/bpf.h>: 这个头文件定义了 BPF 的基本数据类型和函数,如 bpf_map_update_elem、bpf_map_lookup_elem 等。此外,它还定义了 BPF 程序类型(如 BPF_PROG_TYPE_KPROBE)和一些宏(如 BPF_ANY、BPF_NOEXIST 等)。
  2. #include <linux/ptrace.h>: 这个头文件定义了 struct pt_regs 结构体,这个结构体在 BPF 程序中常用于访问 CPU 寄存器的值。在你的 BPF 程序中,struct pt_regs 是 BPF_KPROBE 和 BPF_KRETPROBE 函数的参数类型。
  3. #include <bpf/bpf_helpers.h>: 这个头文件定义了一些 BPF 辅助函数,如 bpf_printk。在你的 BPF 程序中,bpf_printk 函数用于打印调试信息。
  4. #include <bpf/bpf_tracing.h>: 这个头文件定义了一些宏,这些宏用于从 struct pt_regs 结构体中提取参数和返回值。在你的 BPF 程序中,例如 PT_REGS_PARM1(ctx) 和 PT_REGS_RC(ctx) 使用了这个头文件提供的宏。
SEC("uprobe//proc/self/exe:uprobed_sub")

SEC 宏用于定义 BPF 程序的一个 section,这个 section 的名字是 "uprobe//proc/self/exe:uprobed_sub"。

"uprobe//proc/self/exe:uprobed_sub" 这个字符串有三部分:

  1. "uprobe": 这表示这个 section 是一个 uprobe 类型的 BPF 程序。
  2. "/proc/self/exe": 这是目标程序的路径。/proc/self/exe 是一个在 Linux 系统中的特殊路径,它是一个符号链接,指向当前进程的可执行文件。
  3. "uprobed_sub": 这是目标程序中要跟踪的函数的名字。

所以,SEC("uprobe//proc/self/exe:uprobed_sub") 这段代码的意思是:定义一个 uprobe 类型的 BPF 程序,用于在当前进程的可执行文件中的 uprobed_sub 函数入口处设置一个跟踪点。

BPF_KPROBE&BPF_KRETPROBE

BPF_KPROBE 是一个宏,用于定义 BPF (Berkeley Packet Filter) 程序中的 kprobe 或者 uprobe 函数。这个宏在 BPF 辅助库(如 libbpf)中定义。

当 BPF_KPROBE 用于定义一个函数时,它接受两个参数:

  1. 第一个参数是你的 BPF 函数的名字。
  2. 第二个参数及其后的参数是你的 BPF 函数的参数。

在实际使用中,BPF_KPROBE 定义的函数会在相应的 kprobe 或 uprobe 事件发生时被调用,例如,当目标内核函数(对于 kprobe)或用户空间函数(对于 uprobe)被调用时。这个函数的参数通常包含一个 struct pt_regs *ctx 指针,这个指针可以用于访问 CPU 寄存器的值。

请注意,BPF_KPROBE 只是一个宏,实际的函数定义需要由你自己提供。这个宏只是用于帮助你更容易地创建 BPF 程序。

用户态程序分析
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2020 Facebook */
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "uprobe.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
	return vfprintf(stderr, format, args);
}

ssize_t get_uprobe_offset(const void *addr)
{
	size_t start, end, base;
	char buf[256];
	bool found = false;
	FILE *f;

	f = fopen("/proc/self/maps", "r");
	if (!f)
		return -errno;

	while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) {
		if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) {
			found = true;
			break;
		}
	}

	fclose(f);

	if (!found)
		return -ESRCH;

	return (uintptr_t)addr - start + base;
}

/* It's a global function to make sure compiler doesn't inline it. */
int uprobed_add(int a, int b)
{
	return a + b;
}

int uprobed_sub(int a, int b)
{
	return a - b;
}

int main(int argc, char **argv)
{
	struct uprobe_bpf *skel;
	long uprobe_offset;
	int err, i;

	/* Set up libbpf errors and debug info callback */
	libbpf_set_print(libbpf_print_fn);

	/* Load and verify BPF application */
	skel = uprobe_bpf__open_and_load();
	if (!skel) {
		fprintf(stderr, "Failed to open and load BPF skeleton\n");
		return 1;
	}

	/* uprobe/uretprobe expects relative offset of the function to attach
	 * to. This offset is relateve to the process's base load address. So
	 * easy way to do this is to take an absolute address of the desired
	 * function and substract base load address from it.  If we were to
	 * parse ELF to calculate this function, we'd need to add .text
	 * section offset and function's offset within .text ELF section.
	 */
	uprobe_offset = get_uprobe_offset(&uprobed_add);

	/* Attach tracepoint handler */
	skel->links.uprobe_add =
		bpf_program__attach_uprobe(skel->progs.uprobe_add, false /* not uretprobe */,
					   0 /* self pid */, "/proc/self/exe", uprobe_offset);

	if (!skel->links.uprobe_add) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* we can also attach uprobe/uretprobe to any existing or future
	 * processes that use the same binary executable; to do that we need
	 * to specify -1 as PID, as we do here
	 */
	skel->links.uretprobe_add =
		bpf_program__attach_uprobe(skel->progs.uretprobe_add, true /* uretprobe */,
					   -1 /* any pid */, "/proc/self/exe", uprobe_offset);

	if (!skel->links.uretprobe_add) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* Let libbpf perform auto-attach for uprobe_sub/uretprobe_sub
	 * NOTICE: we provide path and symbol info in SEC for BPF programs
	 */
	err = uprobe_bpf__attach(skel);
	if (err) {
		fprintf(stderr, "Failed to auto-attach BPF skeleton: %d\n", err);
		goto cleanup;
	}

	printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
	       "to see output of the BPF programs.\n");

	for (i = 0;; i++) {
		/* trigger our BPF programs */
		fprintf(stderr, ".");
		uprobed_add(i, i + 1);
		uprobed_sub(i * i, i);
		sleep(1);
	}

cleanup:
	uprobe_bpf__destroy(skel);
	return -err;
}
功能说明
  • get_uprobe_offset: 这是一个辅助函数,用于通过读取 /proc/self/maps 文件来计算 uprobes 需要的函数偏移量。这个文件包含了当前进程内存映射的信息。
  • uprobed_add 和 uprobed_sub: 这两个函数是会被 uprobes 附加的目标。在这个例子中,他们都是简单的函数,一个执行加法,另一个执行减法。
  • main 函数: 这是程序的主入口点。
  • 首先,它设置了 libbpf 的错误和调试信息回调,然后加载并验证 BPF 程序。
  • 接下来,它调用 get_uprobe_offset 函数计算 uprobed_add 函数的偏移量,然后将 uprobes 附加到这个偏移量上。其中 bpf_program__attach_uprobe 函数用于将 Uprobe 附加到指定的程序上。
  • 然后,它尝试将一个返回 Uprobe 附加到同一个偏移量上,但这次是为任何使用相同二进制可执行文件的现有或未来的进程。
  • 然后,它使用 uprobe_bpf__attach 函数让 libbpf 自动附加 uprobe_sub 和 uretprobe_sub。
  • 最后,它进入一个无限循环,定期触发这两个被 uprobes 附加的函数。
/proc/self/maps 文件

/proc/self/maps 文件是 Linux 中的一个虚拟文件,它包含了当前进程的内存映射信息。这个文件是只读的,它列出了进程的各种内存区域,包括可执行文件的各个段(如代码段、数据段)、动态库的映射、堆、栈、内核映射等。

每一行代表一个内存区域,以下是一行的格式示例:

00400000-0040b000 r-xp 00000000 08:01 1971274    /bin/cat

这一行中的各部分含义如下:

  • 00400000-0040b000: 这是内存区域的起始和结束地址。
  • r-xp: 这表明该内存区域的权限。r 代表可读,w 代表可写,x 代表可执行,p 代表私有(写时复制)。
  • 00000000: 这是该区域在文件中的偏移量。
  • 08:01: 这是该区域所在设备的主设备号和次设备号。
  • 1971274: 这是该区域相关联的 inode 号。
  • /bin/cat: 如果该区域是映射到了文件,这里会显示文件的路径。

如果你想查看其他进程的内存映射信息,你可以通过 /proc/<pid>/maps 来查看,其中 <pid> 是你感兴趣的进程的进程 ID。不过,要注意的是,只有当你拥有足够的权限时(通常是 root 权限),你才能查看其他进程的内存映射信息。

在代码中

f = fopen("/proc/self/maps", "r");
if (!f)
    return -errno;

while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) {
    if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) {
        found = true;
        break;
    }
}

本质上就是用函数的当前地址,在/proc/self/maps 中找到本函数的执行的内存区域,然后用这个地址去换算偏移量

skel->links.uprobe_add =
		bpf_program__attach_uprobe(skel->progs.uprobe_add, false /* not uretprobe */,
					   0 /* self pid */, "/proc/self/exe", uprobe_offset);

中对uprobe_add 函数进行绑定

执行效果

uprobe-53665   [004] d..21 72478.224978: bpf_trace_printk: uprobed_sub ENTRY: a = 49, b = 7
uprobe-53665   [004] d..21 72478.224979: bpf_trace_printk: uprobed_sub EXIT: return = 42
uprobe-53665   [004] d..21 72479.225086: bpf_trace_printk: uprobed_add ENTRY: a = 8, b = 9
uprobe-53665   [004] d..21 72479.225090: bpf_trace_printk: uprobed_add EXIT: return = 17
uprobe-53665   [004] d..21 72479.225091: bpf_trace_printk: uprobed_sub ENTRY: a = 64, b = 8
uprobe-53665   [004] d..21 72479.225093: bpf_trace_printk: uprobed_sub EXIT: return = 56
uprobe-53665   [004] d..21 72480.225396: bpf_trace_printk: uprobed_add ENTRY: a = 9, b = 10
uprobe-53665   [004] d..21 72480.225400: bpf_trace_printk: uprobed_add EXIT: return = 19
uprobe-53665   [004] d..21 72480.225401: bpf_trace_printk: uprobed_sub ENTRY: a = 81, b = 9
uprobe-53665   [004] d..21 72480.225402: bpf_trace_printk: uprobed_sub EXIT: return = 72
uprobe-53665   [004] d..21 72481.225544: bpf_trace_printk: uprobed_add ENTRY: a = 10, b = 11
uprobe-53665   [004] d..21 72481.225547: bpf_trace_printk: uprobed_add EXIT: return = 21
uprobe-53665   [004] d..21 72481.225549: bpf_trace_printk: uprobed_sub ENTRY: a = 100, b = 10
uprobe-53665   [004] d..21 72481.225550: bpf_trace_printk: uprobed_sub EXIT: return = 90
uprobe-53665   [004] d..21 72482.225697: bpf_trace_printk: uprobed_add ENTRY: a = 11, b = 12
uprobe-53665   [004] d..21 72482.225702: bpf_trace_printk: uprobed_add EXIT: return = 23
uprobe-53665   [004] d..21 72482.225705: bpf_trace_printk: uprobed_sub ENTRY: a = 121, b = 11
uprobe-53665   [004] d..21 72482.225707: bpf_trace_printk: uprobed_sub EXIT: return = 110
uprobe-53665   [004] d..21 72483.225810: bpf_trace_printk: uprobed_add ENTRY: a = 12, b = 13
uprobe-53665   [004] d..21 72483.225815: bpf_trace_printk: uprobed_add EXIT: return = 25
uprobe-53665   [004] d..21 72483.225818: bpf_trace_printk: uprobed_sub ENTRY: a = 144, b = 12
uprobe-53665   [004] d..21 72483.225819: bpf_trace_printk: uprobed_sub EXIT: return = 132
uprobe-53665   [004] d..21 72484.225973: bpf_trace_printk: uprobed_add ENTRY: a = 13, b = 14
uprobe-53665   [004] d..21 72484.225978: bpf_trace_printk: uprobed_add EXIT: return = 27
uprobe-53665   [004] d..21 72484.225980: bpf_trace_printk: uprobed_sub ENTRY: a = 169, b = 13
uprobe-53665   [004] d..21 72484.225982: bpf_trace_printk: uprobed_sub EXIT: return = 156

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值