notes/article/ebpf/eBPF示例:x86-64平台上的uprobe.md

8.0 KiB
Raw Permalink Blame History

eBPF示例x86-64平台上的uprobe

[toc]

重点示例代码中只适用于uprobe没有strip的可执行文件如果可执行文件被strip了怎么办

回顾

上节视频详细讲解了kprobe的原理和在 libbpf-bootstrap 框架上的示例代码分析;

这节视频讲下 libbpf-bootstrap 框架上的 uprobe 示例代码;

libbpf-bootstrap 中的 uprobe 示例代码

# 代码路径: libbpf-bootstrap/examples/c/
uprobe.bpf.c  uprobe.c

uprobe 要能正常工作需要满足2个条件

  • 被attach的接口名称
  • 接口所在可执行文件路径

在 libbpf-bootstrap 框架中有2种写法

  1. 在内核层的ebpf程序中通过SEC() 给ebpf提供 被attach的接口名称和接口所在的文件路径
  2. 在用户层的ebpf程序中通过 bpf_program__attach_uprobe_opts 接口提供 被attach的接口名称和接口所在的文件路径

改造示例代码

为了简单起见,uprobed_sub 改成和 uprobe_add 同一种写法;

为了模拟更真实的使用场景,把示例代码中的 uprobed_adduprobed_sub 独立到一个测试代码中:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* 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[])
{
    int i = 0;

    for (i = 0;; i++) {
		/* trigger our BPF programs */
		uprobed_add(i, i + 1);
		uprobed_sub(i * i, i);

		/* 为了好验证结果 */
		if (i >= 10) {
			i = 0;
		}

		sleep(1);
	}
}

再改造下 uprobe.c 代码,把 uprobed_adduprobed_sub 所在的测试代码进程ID通过参数传进来

// 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);
}

int main(int argc, char **argv)
{
	struct uprobe_bpf *skel;
	int err, i, attach_pid;
	char binary_path[256] = {};
	LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);

	if (2 != argc) {
		fprintf(stderr, "usage:%s attach_pid\n", argv[0]);
		return -1;
	}

	attach_pid = atoi(argv[1]);
	sprintf(binary_path, "/proc/%d/exe", attach_pid);

	/* 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;
	}

	/* Attach tracepoint handler */
	uprobe_opts.func_name = "uprobed_add";
	uprobe_opts.retprobe = false;
	/* uprobe/uretprobe expects relative offset of the function to attach
	 * to. libbpf will automatically find the offset for us if we provide the
	 * function name. If the function name is not specified, libbpf will try
	 * to use the function offset instead.
	 */
	skel->links.uprobe_add = bpf_program__attach_uprobe_opts(skel->progs.uprobe_add,
								 attach_pid, binary_path,
								 0 /* offset for function */,
								 &uprobe_opts /* opts */);
	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
	 */
	uprobe_opts.func_name = "uprobed_add";
	uprobe_opts.retprobe = true;
	skel->links.uretprobe_add = bpf_program__attach_uprobe_opts(
		skel->progs.uretprobe_add, attach_pid, binary_path,
		0 /* offset for function */, &uprobe_opts /* opts */);
	if (!skel->links.uretprobe_add) {
		err = -errno;
		fprintf(stderr, "Failed to attach uprobe: %d\n", err);
		goto cleanup;
	}

	/* Attach tracepoint handler */
	uprobe_opts.func_name = "uprobed_sub";
	uprobe_opts.retprobe = false;
	/* uprobe/uretprobe expects relative offset of the function to attach
	 * to. libbpf will automatically find the offset for us if we provide the
	 * function name. If the function name is not specified, libbpf will try
	 * to use the function offset instead.
	 */
	skel->links.uprobe_sub = bpf_program__attach_uprobe_opts(skel->progs.uprobe_sub,
								 attach_pid, binary_path,
								 0 /* offset for function */,
								 &uprobe_opts /* opts */);
	if (!skel->links.uprobe_sub) {
		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
	 */
	uprobe_opts.func_name = "uprobed_sub";
	uprobe_opts.retprobe = true;
	skel->links.uretprobe_sub = bpf_program__attach_uprobe_opts(
		skel->progs.uretprobe_sub, attach_pid, binary_path,
		0 /* offset for function */, &uprobe_opts /* opts */);
	if (!skel->links.uretprobe_sub) {
		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, ".");
		sleep(1);
	}

cleanup:
	uprobe_bpf__destroy(skel);
	return -err;
}

问题可执行文件被strip找不到被attach的接口符号怎么办

  1. 在 strip 可执行文件前,先备份一下;
  2. 对没有strip的可执行文件进行反汇编并查找 uprobe_add 和 uprobe_sub 符号的汇编指令地址:
objdump -d test_uprobe.unstriped

000000000000064a <uprobed_add>:
000000000000065e <uprobed_sub>:
  1. 通过命令 cat /proc/attach_pid/maps (attach_pid 是uprobe需要跟踪的进程ID) 获取有可执行属性的attach_pid 的偏移地址:
起始地址     -结束地址       属性 偏移地址  主从设备号 inode编号                文件名
55fde9ec5000-55fde9ec6000 r-xp 00000000 08:01 32672410                   test_uprobe
55fdea0c5000-55fdea0c6000 r--p 00000000 08:01 32672410                   test_uprobe
55fdea0c6000-55fdea0c7000 rw-p 00001000 08:01 32672410                   test_uprobe
7fb077d9b000-7fb077f82000 r-xp 00000000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb077f82000-7fb078182000 ---p 001e7000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb078182000-7fb078186000 r--p 001e7000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb078186000-7fb078188000 rw-p 001eb000 103:02 11558371                  /lib/x86_64-linux-gnu/libc-2.27.so
7fb078188000-7fb07818c000 rw-p 00000000 00:00 0 
7fb07818c000-7fb0781b5000 r-xp 00000000 103:02 11543405                  /lib/x86_64-linux-gnu/ld-2.27.so
7fb07838b000-7fb07838d000 rw-p 00000000 00:00 0 
7fb0783b5000-7fb0783b6000 r--p 00029000 103:02 11543405                  /lib/x86_64-linux-gnu/ld-2.27.so
7fb0783b6000-7fb0783b7000 rw-p 0002a000 103:02 11543405                  /lib/x86_64-linux-gnu/ld-2.27.so
7fb0783b7000-7fb0783b8000 rw-p 00000000 00:00 0 
7ffed92ea000-7ffed930b000 rw-p 00000000 00:00 0                          [stack]
7ffed9383000-7ffed9386000 r--p 00000000 00:00 0                          [vvar]
7ffed9386000-7ffed9388000 r-xp 00000000 00:00 0                          [vdso]
7fffffffe000-7ffffffff000 --xp 00000000 00:00 0                          [uprobes]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
  1. uprobe.c 文件中的 bpf_program__attach_uprobe_opts 接口:
uprobe_opts.func_name 不要赋值,因为二进制文件中已经找不到函数对应的符号
func_offset 参数赋值 = 第2步获取的函数符号汇编指令地址 + 第3步获取的偏移地址