6.9 KiB
6.9 KiB
eBPF示例:x86-64平台上的kprobe
[toc]
回顾
前面2节视频讲了 eBPF基础 和 libbpf-bootstrap基础,这节视频讲下 libbpf-bootstrap中kprobe在x86-64平台上的示例;
重点讲:
- BPF_KPROBE 宏展开
- 低版本内核,不支持CO-RE,如何重新实现kprobe示例功能
kprobe作用
可以在几乎所有的函数中 动态 插入探测点,利用注册的回调函数,知道内核函数是否被调用,被调用上下文,入参以及返回值;
kretprobe 是在 kprobe 的基础上实现;
kprobe原理
- 如果用户没有注册kprobe探测点,指令流:
指令1(instr1)
顺序执行到指令4(instr4)
- 如果用户注册一个kprobe探测点到
指令2(instr2)
,指令2
被备份,并把指令2
的入口点替换为断点指令,断点指令是CPU架构相关,如x86-64是int3,arm是设置一个未定义指令; - 当CPU执行到断点指令时,触发一个
trap
,在trap
流程中,- 首先,执行用户注册的
pre_handler
回调函数; - 然后,单步执行前面备份的
指令2(instr2)
; - 单步执行完成后,执行用户注册的
post_handler
回调函数; - 最后,执行流程回到被探测指令之后的正常流程继续执行;
- 首先,执行用户注册的
参考:
https://blog.csdn.net/Rong_Toa/article/details/116643875
https://yoc.docs.t-head.cn/linuxbook/Chapter4/tracing.html
https://github.com/eunomia-bpf/bpf-developer-tutorial
libbpf-bootstrap 中的 kprobe 示例
通过示例代码来了解kprobe的使用方法:
libbpf-bootstrap/examples/c/ 目录下的:
kprobe.bpf.c
kprobe.c
- kprobe.bpf.c 的功能及演示
- kprobe示例代码的实现逻辑
- BPF_KPROBE 宏展开
- 如果不使用CO-RE,要怎么实现原来的功能
kprobe.bpf.c 的功能及演示
删除文件时,就会打印被删除文件的文件名,以及返回值
rm-12056 [002] .... 4188.004100: 0: KPROBE ENTRY pid = 12056, filename = a.txt
rm-12056 [002] d... 4188.004180: 0: KPROBE EXIT: pid = 12056, ret = 0
do_unlinkat 接口定义
// linux-5.4.150/include/linux/syscalls.h
extern long do_unlinkat(int dfd, struct filename *name);
// linux-5.4.150/include/linux/fs.h
struct filename {
const char *name; /* pointer to actual string */
const __user char *uptr; /* original userland pointer */
int refcnt;
struct audit_names *aname;
const char iname[];
};
kprobe示例代码的实现逻辑
在kprobe.bpf.c 中
SEC("kprobe/do_unlinkat") //在内核的 do_unlinkat 入口处注册一个 kprobe 探测点
SEC("kretprobe/do_unlinkat") //在内核的 do_unlinkat 返回时注册一个 kretprobe 探测点
// 使用 BPF_KPROBE 和 BPF_KRETPROBE 宏来定义探测点的回调函数
kprobe.c 中的 open, load, attach
BPF_KPROBE 宏展开
clang编译器预编译的示例:
clang -E -c test.c -o test.i
clang编译器预编译 kprobe.bpf.c
/home/zhanglong/Desktop/clang-16/clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \
-I.output -I../../libbpf/include/uapi -I../../vmlinux/x86/ -I/home/zhanglong/workdir/disk1/workdir/embeded_work/my_note/eBPF/note/src/x86-64/libbpf-bootstrap/blazesym/include -idirafter /home/zhanglong/workdir/disk1/software/llvm-clang/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04/lib/clang/16/include -idirafter /usr/local/include -idirafter /usr/include/x86_64-linux-gnu -idirafter /usr/include \
-E -c kprobe.bpf.c -o .output/kprobe.tmp.bpf.i
__attribute__((section("kprobe/do_unlinkat"), used))
# 相当于kprobe原理中用户定义的回调函数
int do_unlinkat(struct pt_regs *ctx);
static inline int ____do_unlinkat(struct pt_regs *ctx, int dfd, struct filename *name);
int do_unlinkat(struct pt_regs *ctx)
{
return ____do_unlinkat(ctx, (void *)((ctx)->di), (void *)((ctx)->si));
}
static inline int ____do_unlinkat(struct pt_regs *ctx, int dfd, struct filename *name)
{
pid_t pid;
const char *filename;
pid = bpf_get_current_pid_tgid() >> 32;
filename = ({
typeof((name)->name) __r;
({
bpf_probe_read_kernel((void *)(&__r), sizeof(*(&__r)), (const void *)__builtin_preserve_access_index(&((typeof(((name))))(((name))))->name));
});
__r;
});
({
static const char ____fmt[] = "KPROBE ENTRY pid = %d, filename = %s\n";
bpf_trace_printk(____fmt, sizeof(____fmt), pid, filename);
});
return 0;
}
- 不借助 BPF_KPROBE 宏要怎么实现?
- do_unlinkat 函数名可改吗?
- 参数一定要定义2个吗?只有1个行不行?都不要行不行?
不借助BPF_KPROBE
SEC("kprobe/do_unlinkat")
int do_unlinkat(struct pt_regs *ctx)
{
pid_t pid;
const char *filename;
struct filename *name = (struct filename *)((ctx)->si);
pid = bpf_get_current_pid_tgid() >> 32;
filename = BPF_CORE_READ(name, name);
bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename);
return 0;
}
不使用CO-RE功能
如果内核版本过低,不支持CO-RE,要怎么实现?
// libbpf-bootstrap/examples/c/.output/bpf/bpf_helper_defs.h
long (*bpf_probe_read_user)(void *dst, __u32 size, const void *unsafe_ptr);
long (*bpf_probe_read_kernel)(void *dst, __u32 size, const void *unsafe_ptr);
long (*bpf_probe_read_user_str)(void *dst, __u32 size, const void *unsafe_ptr);
long (*bpf_probe_read_kernel_str)(void *dst, __u32 size, const void *unsafe_ptr);
不使用CO-RE功能的代码实现:
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2021 Sartura */
#define __KERNEL__ //参考: bpf_tracing.h 中的定义
#include <linux/bpf.h> //参考: minimal_legacy.bpf.c
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
//参考: minimal_legacy.bpf.c
typedef unsigned int u32;
typedef int pid_t;
//拷贝: vmlinux/x86/vmlinux.h 中的定义
struct pt_regs {
long unsigned int r15;
long unsigned int r14;
long unsigned int r13;
long unsigned int r12;
long unsigned int bp;
long unsigned int bx;
long unsigned int r11;
long unsigned int r10;
long unsigned int r9;
long unsigned int r8;
long unsigned int ax;
long unsigned int cx;
long unsigned int dx;
long unsigned int si;
long unsigned int di;
long unsigned int orig_ax;
long unsigned int ip;
long unsigned int cs;
long unsigned int flags;
long unsigned int sp;
long unsigned int ss;
};
char LICENSE[] SEC("license") = "Dual BSD/GPL";
SEC("kprobe/do_unlinkat")
int BPF_KPROBE(do_unlinkat, int dfd, void *name)
{
pid_t pid;
const char *filename;
int refcnt;
pid = bpf_get_current_pid_tgid() >> 32;
bpf_probe_read_kernel(&filename, sizeof(filename), name+0);
bpf_probe_read_kernel(&refcnt, sizeof(refcnt), name+16);
bpf_printk("KPROBE ENTRY pid = %d, refcnt=%d filename = %s\n", pid, refcnt, filename);
return 0;
}