impl uprobe

This commit is contained in:
yunwei37
2022-12-05 00:11:54 +08:00
parent 40260892b4
commit f05a840fce
3 changed files with 121 additions and 71 deletions

View File

@@ -4,6 +4,35 @@ eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网
本文是 eBPF 入门开发实践指南的第二篇,主要介绍 eBPF 的基本框架和开发流程。
## 下载安装 eunomia-bpf 开发工具
可以通过以下步骤下载和安装 eunomia-bpf
下载 ecli 工具,用于运行 eBPF 程序:
```console
$ wget https://aka.pw/bpf-ecli -O ecli && chmod +x ./ecli
$ ./ecli -h
Usage: ecli [--help] [--version] [--json] [--no-cache] url-and-args
```
下载编译器工具链,用于将 eBPF 内核代码编译为 config 文件或 WASM 模块:
```console
$ wget https://github.com/eunomia-bpf/eunomia-bpf/releases/latest/download/eunomia.tar.gz
$ tar -xvf eunomia.tar.gz -C ~
$ export PATH=$PATH:~/.eunomia/bin
$ ecc -h
eunomia-bpf compiler
Usage: ecc [OPTIONS] <SOURCE_PATH> [EXPORT_EVENT_HEADER]
```
也可以使用 docker 镜像进行编译:
```console
$ docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest # 使用 docker 进行编译。`pwd` 应该包含 *.bpf.c 文件和 *.h 文件。
```
## Hello World - minimal eBPF program
```c
@@ -38,13 +67,21 @@ int handle_tp(void *ctx)
$ ecc hello.bpf.c
Compiling bpf object...
Packing ebpf object and config into package.json...
```console
```
或使用 docker 镜像进行编译:
```shell
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest
```
然后使用 ecli 运行编译后的程序:
```console
$ sudo ecli ./package.json
Runing eBPF program...
```
运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
```console

View File

@@ -1,79 +1,105 @@
---
layout: post
title: bootstrap
date: 2022-10-10 16:18
category: bpftools
author: yunwei37
tags: [bpftools, examples, uprobe, perf event]
summary: an example of a simple (but realistic) BPF application prints bash commands from all running bash shells on the system.
---
# eBPF 入门开发实践指南五:使用 uprobe 捕获 bash 的 readline 函数调用
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
本文是 eBPF 入门开发实践指南的第五篇,主要介绍如何使用 uprobe 捕获 bash 的 readline 函数调用。
This prints bash commands from all running bash shells on the system.
## 使用 uprobe 捕获 bash 的 readline 函数调用
## System requirements:
uprobe 是一种用于捕获用户空间函数调用的 eBPF 的探针,我们可以通过它来捕获用户空间程序调用的系统函数。
- Linux kernel > 5.5
- Eunomia's [ecli](https://github.com/eunomia-bpf/eunomia-bpf/tree/master/ecli) installed
例如,我们可以使用 uprobe 来捕获 bash 的 readline 函数调用,从而获取用户在 bash 中输入的命令行。示例代码如下:
```c
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2021 Facebook */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bashreadline.h"
## Run
#define TASK_COMM_LEN 16
#define MAX_LINE_SIZE 80
- Compile:
/* Format of u[ret]probe section definition supporting auto-attach:
* u[ret]probe/binary:function[+offset]
*
* binary can be an absolute/relative path or a filename; the latter is resolved to a
* full binary path via bpf_program__attach_uprobe_opts.
*
* Specifying uprobe+ ensures we carry out strict matching; either "uprobe" must be
* specified (and auto-attach is not possible) or the above format is specified for
* auto-attach.
*/
SEC("uprobe//bin/bash:readline")
int BPF_KRETPROBE(printret, const void *ret) {
char str[MAX_LINE_SIZE];
char comm[TASK_COMM_LEN];
u32 pid;
```shell
docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest
```
if (!ret)
return 0;
or
bpf_get_current_comm(&comm, sizeof(comm));
```shell
ecc bashreadline.bpf.c bashreadline.h
```
pid = bpf_get_current_pid_tgid() >> 32;
bpf_probe_read_user_str(str, sizeof(str), ret);
- Run:
```console
$ sudo ./ecli run eunomia-bpf/examples/bpftools/bootstrap/package.json
TIME PID STR
11:17:34 28796 whoami
11:17:41 28796 ps -ef
11:17:51 28796 echo "Hello eBPF!"
```
## details in bcc
bpf_printk("PID %d (%s) read: %s ", pid, comm, str);
return 0;
};
char LICENSE[] SEC("license") = "GPL";
```
Demonstrations of bashreadline, the Linux eBPF/bcc version.
This prints bash commands from all running bash shells on the system. For
example:
这段代码的作用是在 bash 的 readline 函数返回时执行指定的 BPF_KRETPROBE 函数,即 printret 函数。
# ./bashreadline
TIME PID COMMAND
05:28:25 21176 ls -l
05:28:28 21176 date
05:28:35 21176 echo hello world
05:28:43 21176 foo this command failed
05:28:45 21176 df -h
05:29:04 3059 echo another shell
05:29:13 21176 echo first shell again
在 printret 函数中,我们首先获取了调用 readline 函数的进程的进程名称和进程 ID然后通过 bpf_probe_read_user_str 函数读取了用户输入的命令行字符串,最后通过 bpf_printk 函数打印出进程 ID、进程名称和输入的命令行字符串。
When running the script on Arch Linux, you may need to specify the location
of libreadline.so library:
除此之外,我们还需要通过 SEC 宏来定义 uprobe 探针,并使用 BPF_KRETPROBE 宏来定义探针函数。
# ./bashreadline -s /lib/libreadline.so
TIME PID COMMAND
11:17:34 28796 whoami
11:17:41 28796 ps -ef
11:17:51 28796 echo "Hello eBPF!"
在 SEC 宏中,我们需要指定 uprobe 的类型、要捕获的二进制文件的路径和要捕获的函数名称。例如,上面的代码中的 SEC 宏的定义如下:
```c
SEC("uprobe//bin/bash:readline")
```
这表示我们要捕获的是 /bin/bash 二进制文件中的 readline 函数。
接下来,我们需要使用 BPF_KRETPROBE 宏来定义探针函数,例如:
```c
BPF_KRETPROBE(printret, const void *ret)
```
这里的 printret 是探针函数的名称const void *ret 是探针函数的参数,它代表被捕获的函数的返回值。
编译运行上述代码:
The entered command may fail. This is just showing what command lines were
entered interactively for bash to process.
```console
$ ecc bashreadline.bpf.c bashreadline.h
Compiling bpf object...
Packing ebpf object and config into package.json...
$ sudo ecli package.json
Runing eBPF program...
```
运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出:
```console
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
PID 12345 (bash) read: ls -l
PID 12345 (bash) read: date
PID 12345 (bash) read: echo "Hello eBPF!"
```
可以看到,我们成功的捕获了 bash 的 readline 函数调用,并获取了用户在 bash 中输入的命令行。
请注意,在上述代码中,我们使用了 SEC 宏来定义了一个 uprobe 探针,它指定了要捕获的用户空间程序 (bin/bash) 和要捕获的函数 (readline)。
此外,我们还使用了 BPF_KRETPROBE 宏来定义了一个用于处理 readline 函数返回值的回调函数 (printret)。该函数可以获取到 readline 函数的返回值,并将其打印到内核日志中。
通过这样的方式,我们就可以使用 eBPF 来捕获 bash 的 readline 函数调用,并获取用户在 bash 中输入的命令行.
It works by tracing the return of the readline() function using uprobes
(specifically a uretprobe).
```

View File

@@ -1,13 +0,0 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2021 Facebook */
#ifndef __BASHREADLINE_H
#define __BASHREADLINE_H
#define MAX_LINE_SIZE 80
struct str_t {
__u32 pid;
char str[MAX_LINE_SIZE];
};
#endif /* __BASHREADLINE_H */