0.写在前面
首先我们要明白,安卓的AOSP包含了海量的代码,他包含了包括了:
1.不同架构下(音响,手机,电视等等各种基于安卓的设备)的上层应用 + 2.Java API Framework(大部分安卓开发者处理的抽象层)+3.C/C++ 底层Lib 以及 Android Runtime环境 +4. HAL外设抽象层(驱动)+ 5.Linux Kernel(最底层)
所以,我们在要以此为大前提去考虑整个开发环境怎么搭建,为什么要那么搭建,以及如何针对具体情况搭建。
平台架构 | Android 开发者 | Android Developers
另外:安卓只是一个mainline,不同厂商可能会各种diy安卓mainline的代码,所以会导致我们搭建环境和测试中在不同的版本有着不同的处理方法,需要我们折腾折腾找解决方案。
1.下载AOSP代码
下载环境依赖
我的云服务器是ubuntu2204,留了400G空间(实际只需要150G),如果要整体编译,确保你的内存在32G及以上。
如果你的/usr/bin只有python3,没有python,请建立软连接或者alias
sudo ln -s /usr/bin/python3 /usr/bin/python
安装依赖:
sudo apt-get update
sudo apt-get install openjdk-8-jdk
sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc unzip
工具:repo
正如谷歌V8一样,大型的项目方往往都维护了自己的构建工具,安卓的AOSP(**Android Open Source Project**),不但在构建中使用了自己的“三层”构建系统,下载源码时甚至都使用**repo**进行跨仓库之间的源码管理。
[repo](https://gerrit.googlesource.com/git-repo/+/refs/heads/master/README.md)
repo其实就是git的封装和针对安卓的客制化,具体我也没太看,应该就是方便不同的库引用别的库的各种东西?大概是方便模块化吧。
下载repo工具
mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/rep
如果你不确定能一次性弄完(在结束当前shell的session前),记着把PATH=~/bin:$PATH<span style="background:#ff4d4f">持久化的写到~/.bashrc中</span>(export或者直接加到PATH里),每次打开bash命令行都会先运行bashrc,加载各种环境变量和alias等等。
初始化,下载源码
因为repo是git的封装,所以git需要的,repo也不能少,确保你设置了git config username和email
mkdir ad_13
cd ad_13
//if 可以科学上网
repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r45
//初始化仓库
//else
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-13.0.0_r45
如果要下11版本就改成android-11.0.0_r45就行了
-b是选择分支,随着代码的更新版本会不断前进,分支就是为了过去的各种版本开辟的一条新的文件track和update,fix的路线。
最后执行 repo sync。
大概下载150G,可能会持续一晚上,下载完以后还会执行git的索引更新,还需要等一段时间,如果突然断开重新运行就行了,下载的东西以及被cache住了。
下载源代码 | Android 开源项目 | Android Open Source Project
2.构建ebpf文件和依赖
文件目录架构,初始化工具
下载好以后运行
source build/envsetup.sh
来初始化编译需要的工具的环境。
进入repo目录可以看到架构如下
art:Android Runtime,一种App运行模式,区别于传统的Dalvik虚拟机,旨在提高Android系统的流畅性
bionic:基础C库源代码,Android改造的C/C++库
bootable:Android程序启动导引,适合各种bootloader的通用代码,包括一个recovery目录
build:存放系统编译规则及generic等基础开发包配置
cts: Android兼容性测试套件标准
dalvik:Android Dalvik虚拟机相关内容
developers:Android开发者参考文档
development: Android应用开发基础设施相关
device:Android支持的各种设备及相关配置
external:Android中使用的外部开源库
frameworks:应用程序框架,Android系统核心部分,由Java和C++编写
hardware:硬件适配接口
kernel:Linux Kernel,不过Android默认不提供,需要单独下载,只有一个tests目录
libcore:Android Java核心类库
libnativehelper:Android动态库,实现JNI库的基础
packages:应用程序包
pdk:Plug Development Kit 的缩写,本地开发套件
platform_testing:Android平台测试程序
prebuilts:x86和arm架构下预编译的一些资源
sdk:Android的Java层sdk
system:Android底层文件系统库、应用和组件
test:Android Vendor测试框架
toolchain:Android工具链文件
ebpf组件就在/system/bpf中,其中有个bpfloader,**这个可以帮助我们热插拔ebpf文件**。
同时aosp也有自己的ebpf测试文件在/system/bpfprogs中有个time_in_state,如果遇到问题可以看看这个示例程序是怎么写的。
创建
进入/external下,创建我们的mybpf文件夹。
创建我们的两个bpf文件(使用libbpf, libbpf_bcc
和 libbpf_android
等进行开发)和Android.bp
构建文件。
- 1.bpf_cli.cpp(在userspace接收ebpf的map信息)
#include <android-base/macros.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <bpf/BpfMap.h>
#include <bpf/BpfUtils.h>
#include <libbpf_android.h>
#include "libbpf.h"
int main() {
constexpr const char tp_prog_path[] = "/sys/fs/bpf/prog_bpf_test_tracepoint_sched_sched_switch";
constexpr const char tp_map_path[] = "/sys/fs/bpf/map_bpf_test_cpu_pid_map";
// Attach tracepoint and wait for 4 seconds
int mProgFd = bpf_obj_get(tp_prog_path);
// int mMapFd = bpf_obj_get(tp_map_path);
bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
sleep(1);
android::bpf::BpfMap<int, int> myMap(tp_map_path);
while(1) {
usleep(40000);
// Read the map to find the last PID that ran on CPU 0
// android::bpf::BpfMap<int, int> myMap(mMapFd);
printf("last PID running on CPU %d is %d\n", 0, *myMap.readValue(0));
}
exit(0);
}
- 2.bpf_test.c
#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>
DEFINE_BPF_MAP(cpu_pid_map, ARRAY, int, uint32_t, 1024);
struct switch_args {
unsigned long long ignore;
char prev_comm[16];
int prev_pid;
int prev_prio;
long long prev_state;
char next_comm[16];
int next_pid;
int next_prio;
};
// SEC("tracepoint/sched/sched_switch")
DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_NET_ADMIN, tp_sched_switch) (struct switch_args* args) {
// int tp_sched_switch(struct switch_args* args) {
int key;
uint32_t val;
key = bpf_get_smp_processor_id();
val = args->next_pid;
bpf_cpu_pid_map_update_elem(&key, &val, BPF_ANY);
return 0;
}
// char _license[] SEC("license") = "GPL";
LICENSE("Apache 2.0");
3.Android.bp
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}
cc_binary {
name: "bpf_cli",
cflags: [
"-Wall",
"-Werror",
"-Wthread-safety",
],
clang: true,
shared_libs: [
"libcutils",
"libbpf_android",
"libbase",
"liblog",
"libnetdutils",
"libbpf_bcc",
],
static_libs: [
"libbpf",
],
srcs: [
"bpf_cli.cpp",
],
}
构建
在最开始的时候我们也说了,大型项目往往有自己的构建系统。比如Linux需要deconfig,Makefile,Kconfig,.config
详情可以看我的这篇文章Linux从模块化机制学到Kconfig,Makefile构建配置原理_makefile与kconfig_LiujiaHuan13的博客-CSDN博客
那么首先我们在庞大源码下要有个“指路人”,指引在不同的条件编译中,如何编译类似硬件厂商提供的deconfig,我们需要
lunch aosp_arm64-eng
来选择指定架构。
这里主要是针对整体构建,由于我们只build自己的文件,所以有没有这一步无所谓,以防万一,还是lunch上好。
接着,打开/externel/libbpf的Android.bp,在“visibility”
中加上"//external/mybpf"
,使得libbpf在构件中对我们的Android.bp可见。
1.如果遇到问题就把对应报错的库中的Android.bp中的visibility加上我们的文件夹路径。
2.我在尝试的过程中发现aosp13无法以共享库.so的方式使用libbpf,不然会报错‘missing variant’
最后我们分别在
- /system/bpf中执行
mma
(构建当前目录所有内容) - repo根目录 执行
m bpf_cli
和m bpf_test.o
这一块非常慢,不知道为什么,可能第一次编译,且从根目录,搜索和索引占了很长时间?而且这里很容易报错,具体报错就慢慢折腾吧(偷摸一把辛酸泪)
关于Android.bp
在Android 7 之前,使用的是我们常用的Makefile,后来换成了”Soong“构建系统
Soong 构建系统 | Android 开源项目 | Android Open Source Project,Makefile也就变成了Android.bp,和Linux一样,从最底层到上层每一层都有Android.bp文件。
3. 实机调试
设备:
中兴远航41 安卓13
导出文件
这时候用scp导出
out/target/product/generic/system/etc
中的bpf_test.o
和out/target/product/generic/system/bin
的bpf_cli,和bpfloader(热加载用)- 还有
out/target/product/generic/system/lib
(依赖库)
具体scp怎么用,google或者csdn吧。。。写不动了
安装工具
这里我使用的是windows平台
其实整个过程都可以在windows上完成,其余的用vscode连远程服务器操作,scp在windows的中端也能用
安装 https://download.mydrivers.online/file/mydriversonline/adb-setup-1.4.3.zip adb工具,如果提示你是否安装驱动一定要yes,不然fastboot程序识别不到devices。
导入ebpf文件
- 在命令行中进入到你scp导出文件的目录,运行adb remount,如果提示你failed,如果你用的是中兴手机,中兴远航41,确定你打开了OEM,那么这个时候不用去网上找教程
- 只需要
adb reboot bootloader
,然后fastboot reboot recovery
进入恢复分区,这个时候看你手机上有一个”高级设置“然后点击”挂载系统目录“,返回上层,重启。(fastboot如果报错看看是否fastboot devices
能识别到?如果识别不到看看设备管理器是否识别为fastboot设备,还是只是其他设备,如果是其他设备那么应该是驱动问题)
- 然后重新
adb remount
,然后进入adb shell
,接着chmod 777 /system
,exit退出来, - push文件,如果还没权限,重试remount和chmod 777
adb push bpf_test.o /system/etc/bpf/
adb push bpf_cli bpfloader /data/local/tmp
adb push ./lib/* /data/local/tmp/
- 赋予程序运行权限
chmod 777 ./bpf_cli ./bpfloader
热加载ebpf
因为ebpf文件会在启动的时候自动加载,但是我们重新启动,/system分区会被重新加载
(对,没错,所谓的chmod 777 可读写只是暂时的,但是我们生产中会把我们的ebpf文件放在源码中重新build,这样开机就能自动运行了)
运行./bpfloader进行热加载,此时看/sys/fs/bpf/会看到map_bpf_test_cpu_pid_map文件成功被建立。
然后运行./bpf_cli,如果报错缺少运行时库,就export一下环境变量export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/date/local/tmp/lib
此时就可以看到,这里读取id是0的bug正在解决。
Congratulations!你的开发环境已经成功部署了。走下来这一套,已经迈向了安卓ebpf开发的第一步。也是笔者目前起步的状态。
笔者目前就读于西安邮电大学计算机科学与技术专业,大二,对计算机体系结构和操作系统非常感兴趣,从初中开始靠自驱力和兴趣不断接触相关领域知识。关于我可以看这里about - JiaHuann’s Blog.
Refs
搭建编译环境 | Android 开源项目 | Android Open Source Project
源代码控制工具 | Android 开源项目 | Android Open Source Project
Android 操作系统源代码文档 | Android 开源项目 | Android Open Source Project