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

257 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# eBPF示例x86-64平台上的uprobe
[toc]
**重点示例代码中只适用于uprobe没有strip的可执行文件如果可执行文件被strip了怎么办**
## 回顾
上节视频详细讲解了kprobe的原理和在 libbpf-bootstrap 框架上的示例代码分析;
这节视频讲下 libbpf-bootstrap 框架上的 uprobe 示例代码;
## libbpf-bootstrap 中的 uprobe 示例代码
```shell
# 代码路径: 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_add``uprobed_sub` 独立到一个测试代码中:
```c
#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_add``uprobed_sub` 所在的测试代码进程ID通过参数传进来
```c
// 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 符号的汇编指令地址:
```shell
objdump -d test_uprobe.unstriped
000000000000064a <uprobed_add>:
000000000000065e <uprobed_sub>:
```
3. 通过命令 `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]
```
4. uprobe.c 文件中的 `bpf_program__attach_uprobe_opts` 接口:
```
uprobe_opts.func_name 不要赋值,因为二进制文件中已经找不到函数对应的符号
func_offset 参数赋值 = 第2步获取的函数符号汇编指令地址 + 第3步获取的偏移地址
```