notes/article/ebpf/eBPF:arm平台上的uprobe.md

254 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# eBPFarm平台上的uprobe
[toc]
这节视频讲下在`arm 32`平台上的 ebpf uprobe 的使用方法;和 x86-64 上的使用方法有好些不同点;
Linux 内核版本4.9.88
## 回顾
在上节视频中,交叉编译了 `zlib`, `libelf`, `libbpf-bootstrap`
交叉编译前需要先执行 `build_env.sh`
```shell
#/bin/sh
# 指定交叉编译工具链的绝对地址
export PATH=$PATH:/home/zhanglong/Desktop/imx6ull_dev/sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
# 指定目标平台类型
export ARCH=arm
# 指定交叉编译器
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 指定交叉编译好的 zlib 和 libelf 库的头文件路径
export EXTRA_CFLAGS="-I/home/zhanglong/Desktop/ebpf/note/src/arm32/extra_libs/elfutils-0.189/_install/include -I/home/zhanglong/Desktop/ebpf/note/src/arm32/extra_libs/zlib-1.3/_install/include"
# 指定交叉编译好的 zlib 和 libelf 库(.a)文件路径
export EXTRA_LDFLAGS="-L/home/zhanglong/Desktop/ebpf/note/src/arm32/extra_libs/elfutils-0.189/_install/lib -L/home/zhanglong/Desktop/ebpf/note/src/arm32/extra_libs/zlib-1.3/_install/lib"
```
编译
```shell
source build_env.sh # 只需执行一次
make clean
make uprobe
```
## 移植 x86-64 平台上的uprobe代码
1.`eBPF示例x86-64平台上的uprobe` 这节视频讲解中改造过的代码拷贝过来;
涉及到3个文件:
- `test_uprobe.c`
应用层的测试代码,定义了 `uprobed_add``uprobed_sub` 2个函数然后在 `main` 函数中循环调用;
测试目的ebpf uprobe 在arm 32平台上是否可以正确获取 `uprobed_add``uprobed_sub` 2个函数的入口参数和返回值
- `uprobe.bpf.c`
ebpf 在内核层的代码,定义 `uprobe` 探测点的回调函数:
应用层 `uprobed_add` 函数进入和返回时的回调函数;
应用层 `uprobed_sub` 函数进入和返回时的回调函数;
- `uprobe.c`
ebpf 在应用层的代码,负责把 `uprobe.bpf.c` 编译得到的字节码加载到内核,以及把 `uprobe.bpf.c` 文件中定义的回调函数 attach 到应用层测试代码中函数的 `uprobe` 探测点;
为了简单起见,不再 `strip` 测试代码的可执行程序,直接通过测试代码中的函数名来 `attach`;(如果对这部分内容不清楚的,可以再回去看看 `eBPF示例x86-64平台上的uprobe` 这节视频)
2. 直接编译 测试代码 和 ebpf
```shell
# 编译测试代码
arm-buildroot-linux-gnueabihf-gcc test_uprobe.c -o test_uprobe
# 编译 ebpf
make clean
make uprobe
```
编译好后,直接运行,报错:
```
libbpf: failed to find valid kernel BTF
libbpf: Error loading vmlinux BTF: -3
```
查看BCC文档 https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
BTF 需要 4.18 及以后的内核版本才支持;
3. 修改代码:
`uprobe.bpf.c` 中,去掉 `#include "vmlinux.h"` ,改为 `#include <linux/bpf.h>`
再次编译ebpf报错
```
uprobe.bpf.c:11:5: error: incomplete definition of type 'struct pt_regs'
```
`libbpf-bootstrap/vmlinux/arm/vmlinux.h` 中的 `struct pt_regs` 定义拷贝到 `uprobe.bpf.c` 中;
再次编译ebpf并执行报错
```
libbpf: prog 'uprobe_add': -- BEGIN PROG LOAD LOG --
0: (79) r4 = *(u64 *)(r1 +8)
1: (79) r3 = *(u64 *)(r1 +0)
2: (85) call 2001000000
invalid func 2001000000
-- END PROG LOAD LOG --
```
`bpf_helper_defs.h` 头文件中并没有定义 2001000000 的枚举值;
阅读下 libbpf-bootstrap 开源代码说明文档https://hub.njuu.cf/libbpf/libbpf-bootstrap
**minimal_Legacy**
```
This version of minimal is modified to allow running on even older kernels that
do not allow global variables.
bpf_printk uses global variables unless BPF_NO_GLOBAL_DATA is defined before
including bpf_helpers.h.
```
`uprobe.bpf.c` 文件中,增加:
```c
#define BPF_NO_GLOBAL_DATA
```
再次编译,并执行:
可以正常运行,但是 `uprobed_add` 打印的结果不对;
按照: `eBPF示例x86-64平台上的kprobe` 视频中的方法,改写下`int BPF_KPROBE(uprobe_add, int a, int b)`
```c
int uprobe_add(struct pt_regs *ctx)
{
int a = ctx->uregs[0];
int b = ctx->uregs[1];
bpf_printk("uprobed_add ENTRY: a = %d, b = %d\n", a, b);
return 0;
}
```
`eBPF基础` 视频中讲过ebpf寄存器是64 bit但是arm32平台寄存器是32 bit这2个事实存在冲突用代码来验证下
```c
int uprobe_add(struct pt_regs *ctx)
{
int a = ctx->uregs[0];
int b = ctx->uregs[1];
bpf_printk("uprobed_add ENTRY: a = %d, b = %d sizeof(uregs[0])=%d\n", a, b, sizeof(ctx->uregs[0]));
return 0;
}
/*
打印结果
uprobed_add ENTRY: a = 10, b = 16 sizeof(uregs[0])=8
即内核层ebpf中, long unsigned int 是 64 bit
*/
```
把变量 `a``b` 用64 bit的格式打印出来
```c
int uprobe_add(struct pt_regs *ctx)
{
int a = ctx->uregs[0];
int b = ctx->uregs[1];
bpf_printk("uprobed_add ENTRY: a = 0x%llx, b = 0x%llx\n", a, b);
return 0;
}
/*
打印结果:
uprobed_add ENTRY: a = 0x700000006, b = 0x700000010
*/
```
根据打印结果和 `test_uprobe.c` 代码分析可知,变量 a 的低32bit是 `test_uprobe.c``uprobed_add` 函数的第一个参数高32bit是第二个参数
强制把 `struct pt_regs` 中的 `long unsigned int` 改为 `unsigned int`强制变为32 bit
```c
struct pt_regs {
unsigned int uregs[18];
};
```
结果正确;
再来解决编译警告的问题:
```c
// 修改 libbpf-bootstrap/libbpf/src/bpf_tracing.h
#if defined(bpf_target_arm)
#define ___bpf_kretprobe_args0() ctx
#define ___bpf_kretprobe_args1(x) ___bpf_kretprobe_args0(), (unsigned int)PT_REGS_RC(ctx)
#define ___bpf_kretprobe_args(args...) ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args)
#else
#define ___bpf_kretprobe_args0() ctx
#define ___bpf_kretprobe_args1(x) ___bpf_kretprobe_args0(), (void *)PT_REGS_RC(ctx)
#define ___bpf_kretprobe_args(args...) ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args)
#endif
#if defined(bpf_target_arm)
#define ___bpf_kprobe_args0() ctx
#define ___bpf_kprobe_args1(x) ___bpf_kprobe_args0(), (unsigned int)PT_REGS_PARM1(ctx)
#define ___bpf_kprobe_args2(x, args...) ___bpf_kprobe_args1(args), (unsigned int)PT_REGS_PARM2(ctx)
#define ___bpf_kprobe_args3(x, args...) ___bpf_kprobe_args2(args), (unsigned int)PT_REGS_PARM3(ctx)
#define ___bpf_kprobe_args4(x, args...) ___bpf_kprobe_args3(args), (unsigned int)PT_REGS_PARM4(ctx)
#define ___bpf_kprobe_args5(x, args...) ___bpf_kprobe_args4(args), (unsigned int)PT_REGS_PARM5(ctx)
#define ___bpf_kprobe_args6(x, args...) ___bpf_kprobe_args5(args), (unsigned int)PT_REGS_PARM6(ctx)
#define ___bpf_kprobe_args7(x, args...) ___bpf_kprobe_args6(args), (unsigned int)PT_REGS_PARM7(ctx)
#define ___bpf_kprobe_args8(x, args...) ___bpf_kprobe_args7(args), (unsigned int)PT_REGS_PARM8(ctx)
#define ___bpf_kprobe_args(args...) ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
#else
#define ___bpf_kprobe_args0() ctx
#define ___bpf_kprobe_args1(x) ___bpf_kprobe_args0(), (void *)PT_REGS_PARM1(ctx)
#define ___bpf_kprobe_args2(x, args...) ___bpf_kprobe_args1(args), (void *)PT_REGS_PARM2(ctx)
#define ___bpf_kprobe_args3(x, args...) ___bpf_kprobe_args2(args), (void *)PT_REGS_PARM3(ctx)
#define ___bpf_kprobe_args4(x, args...) ___bpf_kprobe_args3(args), (void *)PT_REGS_PARM4(ctx)
#define ___bpf_kprobe_args5(x, args...) ___bpf_kprobe_args4(args), (void *)PT_REGS_PARM5(ctx)
#define ___bpf_kprobe_args6(x, args...) ___bpf_kprobe_args5(args), (void *)PT_REGS_PARM6(ctx)
#define ___bpf_kprobe_args7(x, args...) ___bpf_kprobe_args6(args), (void *)PT_REGS_PARM7(ctx)
#define ___bpf_kprobe_args8(x, args...) ___bpf_kprobe_args7(args), (void *)PT_REGS_PARM8(ctx)
#define ___bpf_kprobe_args(args...) ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
#endif
```
再重新编译 uprobe
```shell
make clean; make uprobe
```
**ebpf kprobe 和 uprobe 在arm 32平台的处理方法一样这里就不再详细讲了**