Last update 20190601
做些铺垫
本文假设读者已掌握如下内容:
- 熟悉Linux内核编译方法。
- 阅读过博文eBPF架构优势及其应用方向上的畅想。
- 熟悉Git操作
- 熟悉CMake,LLVM,Clang等编译工具
在博文eBPF架构优势及其应用方向上的畅想中有提到eBPF的执行流程,这套在主机系统(泛指基于x86的Linux distribution)上直接apt install或者源码编译安装就可以了。但是在android上怎么执行呢? 现在大部分android设备运行在基于arm的处理器架构上,我们熟悉的高通,MTK,华为海思都属于arm处理器架构。x86上的eBPF工具栈(如前文所述,这里仅指BCC&BPFTrace)程序是无法直接在arm处理器上执行的,需要所谓的交叉编译技术才可以。除了程序本身之外,它所依赖的基础库如libc也同样需要交叉编译才能正确工作。
运行在android时的难题
- BCC及BPFTrace使用的是基于CMake的编译方式,与android使用的gradle,android BP的编译系统是不一致的。
- BCC项目中以python 包为基础,那意味着需要有python运行环境,这在anroid里也是没有的。
可行的思路
- 没条件就创造条件,将BCC&BPFTrace强行适配到android编译环境,使其可以直接运行在android上下文中。中间涉及的依赖,冲突问题需要手动修改。
- 添加中间层,由host端生成的的bytecode通过adb通道派发给client端,具体执行由client端的常驻进程完成。
- android端运行某个linux distribution环境(如Debian,Ubuntu),在手机端编译与安装BCC&BPFTrace。
- 参考BCC&BPFTrace设计思路,依照android的架构实现一套类似功能程序,部分采用BCC&BPFTrace项目代码。
综合利弊之后,本文使用方案3。缺点就是对android的环境要求比较高,它需要:
- 手机能够root,而且可以将data分区remount成可读写。
- android kernel 版本要求在4.9及以上。
- 具有编译android kernel的环境。
步骤1 编译带必要功能的内核
通过手动编内核实现以下两个目的:
- 使能eBPF相关功能(如果已经开启可以跳过此步骤)。
- 获取特定kernel的头文件。BCC会用到此头文件中的结构体来解析eBPF的返回数据。
1:开启以下内核配置到项目_defconfig文件中1
2
3
4
5
6
7
8
9
10
11CONFIG_BPF=y
CONFIG_BPF_JIT=y
CONFIG_HAVE_BPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENT=y
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENT=y
CONFIG_DEBUG_PREEMPT=y
CONFIG_PREEMPTIRQ_EVENTS=y
CONFIG_FTRACE_SYSCALLS=y
2:根据项目情况编译内核
- 如果你有完整android项目代码的话可以make bootimage编译出内核
- 否则配置好交叉编译环境后单独编译内核。
关于获取Linux kernel 头文件:
- 如果你直接使用交叉编译环境来编译内核的话可以忽略这段内容。
- 如果你的内核改动比较少,算是比较”干净”的话可以直接使用别人已经打包好的头文件包。不过这种情况比较少见,嵌入式的linux 内核基本被芯片厂或手机厂有所修改。
- 如果你是用android树来编译内核的话,需要手动编译头文件因为android的打包结构并不保留头文件。编译方法如下:
- cd to kernel tree
- export ARCH=arm64
- export CROSS_COMPILE=aarch64-linux-gnu- (任意交叉编译器都可以)
- make boardname_defconfig
- make -j6
步骤2 安装debian-arm到Android
Github有叫adeb项目,它的功能是将debian-arm整个固件push到android设备的/data目录下,然后并通过本地shell的配合实现了debian环境下shell。也就是debian能支持的功能他都能支持,只是没有屏幕,只能通过终端控制。当然也支持apt命令,通过修改源(apt source)之后下载安装社区提供的各种软件。
- 首先下载adeb项目 git clone https://github.com/joelagnel/adeb.git
- 然后执行安装命令,此时需要手机已经是root,并且确保剩余空间至少大于300MB。adeb的其他命令具体参考reference guide,参考引用2。
- adeb prepare –full –kernelsrc /path/to/kernel-source // 步骤1中提及的kernel路径
- adeb shell
即可进入到基于debian运行环境的shell。 他相比android区别在于只是利用了android中运行的linux kernel而其他标准库之类(bionic,linker等)都替换成debian所提供的libc及linker。安装过程及运行结果如下:
1 | $ adeb prepare --full --kernelsrc ./msm-4.9_valina_sdm845 |
AOSP项目源码仓库中的 external目录 下面谷歌已经集成了adeb项目,如果有AOSP源码的话可以直接使用项目中的源码。这部分更新还未集成到Android P,应该是会随着Android Q一起发布。
Hello world
例11
2
3
4
5
6
7
8root@localhost:/usr/share/bcc/tools# vfsstat
TIME READ/s WRITE/s CREATE/s OPEN/s FSYNC/s
08:45:23: 98 369 0 5 0
08:45:24: 287 261 0 144 0
08:45:25: 115 129 0 49 0
08:45:26: 253 125 0 92 0
08:45:27: 326 272 0 74 0
08:45:28: 217 229 0 61 0
vfsstat(Virtual FileSystem Stats)可以查看下发到虚拟文件系统层的所有IO请求,如果看到以上结果就说明大功告成啦!过程中如果出现问题的话可以参考引用3。我自己遇到过kernel head不匹配与没有开启eBPF导致的错误。
预编译好的bcc工具集目录在 /usr/share/bcc/tools, 目前将近有100多个小工具。
例21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23root@localhost:/usr/share/bcc/tools# tcpdrop
TIME PID IP SADDR:SPORT > DADDR:DPORT STATE (FLAGS)
08:54:36 3257 6 ::ffff:172.28.140.149:80 > ::ffff:183.60.137.144:46922 ESTABLISHED (ACK)
tcp_drop+0x0
tcp_rcv_established+0x2d4
tcp_v4_do_rcv+0x198
tcp_v4_rcv+0xb54
ip_local_deliver_finish+0x10c
ip_local_deliver+0x108
ip_rcv_finish+0x168
ip_rcv+0x344
__netif_receive_skb_core+0x5a8
__netif_receive_skb+0x38
process_backlog+0xd0
net_rx_action+0x258
__softirqentry_text_start+0x15c
do_softirq+0x70
netif_rx_ni+0x80
hdd_rx_packet_cbk+0x438
$x+0x310
$x+0x1e8
kthread+0xf4
ret_from_fork+0x10
查看TCP 掉包时的内核路径以推测掉包原因(TCP 掉包路径非常多,只能打印堆栈来诊断了)。
步骤3(可选) 源码编译安装最新版BCC与BPFTrace
默认的安装方式虽然简单但是所使用的工具版本比较老旧,为了体验最新功能可以下载最新代码并编译安装。需要提示的是以下操作都是在adeb shell中执行,也就是所有操作都在手机端完成,包括源码下载,编译与安装。
安装BCC
- git clone https://github.com/iovisor/bcc.git // 下载bcc项目代码
- cd bcc && rm -rf build && mkdir -p build && cd build //在bcc目录下创建build目录,用于代码编译。
- export CC=clang-6.0 // 设置C编译器
- export CXX=clang++-6.0 //设置C++编译器
- cmake .. -DCMAKE_INSTALL_PREFIX=/usr //运行环境检查
- make -j4 //编译
- make install //安装
安装后的tools路径为”/usr/share/bcc/tools”,运行cachestat检查下是否安装成功。
可能出现的错误:
BCC 20190129版本中运行上面命令时会出现如下错误:1
2
3
4
5
6
7
8Traceback (most recent call last):
File "/usr/share/bcc/tools/cachestat", line 20, in <module>
from bcc import BPF
File "/usr/lib/python2.7/dist-packages/bcc/__init__.py", line 30, in <module>
from .syscall import syscall_name
File "/usr/lib/python2.7/dist-packages/bcc/syscall.py", line 387, in <module>
raise Exception("ausyscall: command not found")
Exception: ausyscall: command not found
解决方法:
安装auditd程序
apt install auditd
如果提示没有找到auditd命令的话,需要手动更新下source list。推荐将 “deb http://ftp.de.debian.org/debian stretch main” 添加到”/etc/apt/sources.list”文件后执行更新。
apt update
安装 BPFTrace
- git clone https://github.com/iovisor/bpftrace.git // 下载bpftrace项目代码
- cd bpftrace && rm -rf build && mkdir -p build && cd build //在bpftrace目录下创建build目录,用于代码编译。
- export CC=clang-6.0 // 设置C编译器
- export CXX=clang++-6.0 //设置C++编译器
- cmake -DCMAKE_BUILD_TYPE=Debug ../ //运行环境检查
- make -j4 //编译
- make install //安装
可能出现的错误1:
无法找到”BPF_FUNC_get_current_cgroup_id”定义!
错误原因是我用的4.9内核中还没有这个定义,是commit 22110ad25b51b0e1f1ece4fcdf21a3738391f018中引入的功能。如果你的内核也是4.9,或者提示没有定义的话可以单笔回退这个提交。
git revert 22110ad25b51b0e1f1ece4fcdf21a3738391f018
可能出现的错误2:
无法找到”bpf_create_map”,是否使用”bcc_createmap”替代?
这是因为bpftrace依赖bcc的库函数,而这个库函数中使用的是bcc开头。规避办法是将bpf相关调用修改成bcc_,修改如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32diff --git a/src/attached_probe.cpp b/src/attached_probe.cpp
index 1837b6a..de9c6e0 100644
--- a/src/attached_probe.cpp
+++ b/src/attached_probe.cpp
@@ -331,7 +331,7 @@ void AttachedProbe::load_prog()
for (int attempt=0; attempt<3; attempt++)
{
- progfd_ = bpf_prog_load(progtype(probe_.type), namep,
+ progfd_ = bcc_prog_load(progtype(probe_.type), namep,
reinterpret_cast<struct bpf_insn*>(insns), prog_len, license,
kernel_version(attempt), log_level, log_buf, log_buf_size);
if (progfd_ >= 0)
diff --git a/src/map.cpp b/src/map.cpp
index 5cfd442..6a452b4 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -46,7 +46,7 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key, int
int value_size = type.size;
int flags = 0;
- mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
+ mapfd_ = bcc_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
if (mapfd_ < 0)
{
std::cerr << "Error creating map: '" << name_ << "'" << std::endl;
@@ -80,7 +80,7 @@ Map::Map(enum bpf_map_type map_type)
std::cerr << "invalid map type" << std::endl;
abort();
}
- mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
+ mapfd_ = bcc_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags);
if (mapfd_ < 0)
{
安装后的tools路径为”/usr/local/share/bpftrace/tools”,运行如下命令验证安装结果:
bpftrace -e ‘kprobe:do_nanosleep { printf(“PID %d sleeping…\n”, pid); }’
写在最后
- 本文介绍的方法适用于系统开发阶段,因为有了debian,所以只要能找到源或者代码,几乎可以执行任何linux 发行版上的工具。
- 后面会陆续介绍其他比较重要的工具(Perf,glances,pidstat,stress等),目前计划还是基于debian的方案。
- 用户固件中不可能会有这套debian的程序,因为他需要root运行,这是最大的缺点。个人比较认同的方案是4,也就是实现适合用于android 环境的类似BCC&BPFTrace工具链,目标是用户固件中也可指直接使用eBPF。
Reference
- “eBPF super powers on ARM64 and Android.pdf” by Joel Fernandes
- https://github.com/joelagnel/adeb/blob/master/README.md
- https://github.com/joelagnel/adeb/blob/master/BCC.md
- https://github.com/iovisor/bcc#tools