作者:Hcamael@知道創宇404實驗室
日期:2022年11月4日
相關閱讀:
在 Android 中開發 eBPF 程序學習總結(二)
最近在研究eBPF,做一下學習筆記。
起因
其實是想學習一下ecapture是怎么實現的,但是實際在我xiaomi 10手機上測試的過程中(已經有root權限)發現,并沒辦法運行,因為ecapture需要內核開啟CONFIG_DEBUG_INFO_BTF,這個配置信息可以通過/proc/config.gz中來查看是否開啟。
我的手機的內核版本是4.19,沒有開啟BTF,但是BPF是開啟了的,接著我繼續查看ecapture的文檔,說如果內核沒有開啟BTF,需要使用make nocore編譯,在github上有提供直接編譯好的nocore安卓版,但是測試還是運行不了。接著自己編譯了一波,但是仍然失敗,感覺可能得嚴格按文檔所述,需要內核版本大于等于5.4。
那么我的手機就沒辦法運行BPF程序了嗎?接著,就開啟了我的研究。
Android BPF demo
在網上搜相關的學習資料,BPF相關的資料本身就挺少的,再過濾一下只限制Android平臺,就更少了。
而且大部分能搜到的中文資料,都是一堆廢話,或者一堆ctrl+c, ctrl+v的文章,實際有用的太少了。安卓官方的資料中也只有一個簡單的demo,而且使用的是Android.bp進行編譯的,還需要本地搭建AOSP環境。
AOSP環境搭建
這破環境真是絕了,掛上daili,我裝了一個晚上還沒好(速度也有4Mb/s了)。然后第二天搜到了能換國內源,下面放一下我的搭建環境的命令:
$ apt-get install -y repo
$ export EPO_URL='https://gerrit-googlesource.proxy.ustclug.org/git-repo'
$ repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b android-12.1.0_r26
$ repo sync -c -j8
簡單的幾句命令就好了,但是要注意,內存建議大于16G,硬盤最好200G以上。
使用AOSP環境編譯程序
# 初始化一下環境變量
$ source build/envsetup.sh
# 初始化一下你想編譯哪個版本的android程序
$ lunch aosp_crosshatch-userdebug
接著后續的測試代碼可以參考測試代碼,該文章中的代碼,在我測試的過程中,沒有啥問題,是能正常運行的,但是在第一次編譯的時候,可能是AOSP架構的問題,會把整個項目都先編譯一次,我安卓也搞的不多,也不知道如果只編譯指定項目。不過在第一編譯之后,后續只需要使用m name,就可以只編譯指定項目了。也是因為要編譯整個項目,如果內存小于16G,是會編譯失敗的,如果本身內存不夠,可以增加一下交換分區的大小。
Android上的BPF
通過這個demo,能看出來,android下使用BPF程序的步驟如下:
首先把編譯好的bpf.o程序放到
/system/etc/bpf/目錄下,這就要求我們需要有/system目錄的可寫權限,但是在我的手機上,就算有root權限了,system目錄也沒辦法寫。所以我把手機的系統從MIUI12,刷成了evolution x系統,然后通過adb shell mount -o rw,remount /來重新掛載根目錄,這樣就能寫/system/etc/bpf目錄了。 使用bpfloader程序,會自動加載/system/etc/bpf目錄下的*.o文件,然后會在/sys/fs/bpf目錄生成相應的prog_xxx和map_xx文件。 我們自己的loader文件需要通過/sys/fs/bpf目錄下的那兩個文件來和BPF程序進行交互。
深入研究Android下的BPF
我根據Linux下的eBPF文件的資料,自己寫了一個DEMO:
BPF程序bpftest.c
#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>
#include <string.h>
#define MAX_ARGV 128;
#define bpf_printk(fmt, args...) bpf_trace_printk(fmt, sizeof(fmt), ##args)
struct event_execv
{
uint32_t pid;
uint32_t gid;
char cmd[80];
};
DEFINE_BPF_MAP(execve_map, ARRAY, uint32_t, struct event_execv, 256);
struct execve_args
{
short common_type;
char common_flags;
char common_preempt_count;
int common_pid;
long __syscall_nr;
unsigned long args[6];
};
SEC("tracepoint/raw_syscalls/sys_enter")
int trace_execve_event(struct execve_args *ctx)
{
struct event_execv event;
uint32_t key = 1;
int comm;
char trace_buf[] = "[Debug] pid = %d, gid = %d, comm=%s\n";
memset(&event, 0, sizeof(event));
event.pid = bpf_get_current_pid_tgid();
event.gid = bpf_get_current_uid_gid();
bpf_execve_map_update_elem(&key, &event, BPF_ANY);
comm = bpf_get_current_comm(&event.cmd, sizeof(event.cmd));
if (comm != 0)
{
return -1;
}
event.cmd[79] = 0;
bpf_printk(trace_buf, event.pid, event.gid, event.cmd);
bpf_execve_map_update_elem(&key, &event, BPF_ANY);
return 0;
}
LICENSE("GPL");
map映射
DEFINE_BPF_MAP是對map相關操作的一個宏定義,可以參考:bpf_helpers.h
#define DEFINE_BPF_MAP_NO_ACCESSORS(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
struct bpf_map_def SEC("maps") the_map = { \
.type = BPF_MAP_TYPE_##TYPE, \
.key_size = sizeof(TypeOfKey), \
.value_size = sizeof(TypeOfValue), \
.max_entries = (num_entries), \
};
#define DEFINE_BPF_MAP(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
DEFINE_BPF_MAP_NO_ACCESSORS(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
\
static inline __always_inline __unused TypeOfValue* bpf_##the_map##_lookup_elem( \
TypeOfKey* k) { \
return unsafe_bpf_map_lookup_elem(&the_map, k); \
}; \
\
static inline __always_inline __unused int bpf_##the_map##_update_elem( \
TypeOfKey* k, TypeOfValue* v, unsigned long long flags) { \
return unsafe_bpf_map_update_elem(&the_map, k, v, flags); \
}; \
\
static inline __always_inline __unused int bpf_##the_map##_delete_elem(TypeOfKey* k) { \
return unsafe_bpf_map_delete_elem(&the_map, k); \
};
比如我上面的代碼為:DEFINE_BPF_MAP(execve_map, ARRAY, uint32_t, struct event_execv, 256);
我的map_name為execve_map,所以這個宏定義幫我定義了bpf_execve_map_update_elem這類的函數,幫我定義了結構體:
struct bpf_map_def SEC("maps") execve_map = {
.type = BPF_MAP_TYPE_##TYPE,
.key_size = sizeof(TypeOfKey),
.value_size = sizeof(TypeOfValue),
.max_entries = (num_entries),
};
并且在/sys/fs/bpf目錄下生成的map文件的結構為:map_(bpf文件名)_(定義的map_name),假如我編譯的bpf文件名為:bpftest.o,放到/system/etc/bpf/目錄下,那么在/sys/fs/bpf目錄下生成的為:map_bpftest_execve_map。
map可以理解為,內核中的BPF和用戶態之間的接口,在內存中是以鍵值對的形式存在的,按我理解,key和value的類型也是可以自己定義的,可以是int,指針,字符串,或者結構體,因為對于BPF來說,key和value就是內存中的一段值,只需要定義好key和value的size就好了,而在上面的結構體中就定義了key和value的大小。
用戶態的loader可以通過/sys/fs/bpf/map_bpftest_execve_map和BPF程序來交換數據。
BPF函數編寫
這塊知識的文章挺多的,在BPF的函數定義的上頭都需要有一個SEC("xxxx"),在最開始的demo中還有另一個寫法,以下兩種寫法是等同的:
SEC("tracepoint/sched/sched_switch")
int tp_sched_switch(struct switch_args* args)
{
......
}
DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_NET_ADMIN, tp_sched_switch) (struct switch_args* args) {
......
}
SEC里面的字符串是為了定義下面的函數是什么類型的BPF程序,因為BPF程序也有很多中類型,比如kprobe, kretprobe, uprobe, uretprobe, tracepoint......。
具體都有啥,可以參見:libbpf.c
再低一點的版本這個結構體的名字叫section_names,不過在我研究了一波之后,我感覺不能通過內核版本來確定我們可以用哪個section,需要通過/sys/kernel/debug/目錄下的情況來確定,但是安卓手機上的情況卻有一些不同,目錄為: /sys/kernel/tracing/,比如我上面代碼中的:SEC("tracepoint/raw_syscalls/sys_enter"),是因為有以下目錄:/sys/kernel/tracing/events/raw_syscalls/sys_enter/,并且struct execve_args結構體是來源于:/sys/kernel/tracing/events/raw_syscalls/sys_enter/format
目前這種方式我覺得只適用于tracepoint,其他的還沒研究到,后續研究到了再補充。
再android上,/sys/fs/bpf/prog_xx的命名方式為:prog_(文件名)_(section名)_(分類,分類名之類的)
比如我的代碼中,文件名為bpftest,section名為tracepoint,tracepoint的分類為raw_syscalls,分類名為sys_enter,所以最后得到的文件為:/sys/fs/bpf/prog_bpftest_tracepoint_raw_syscalls_sys_enter
BPF相關函數
bpf的相關函數可以參考bpf_helper_defs.h文件,比如上述的bpf_get_current_pid_tgid,表示獲取觸發該BPF的程序的pid,bpf_get_current_uid_gid是獲取用戶的gid,bpf_get_current_comm是獲取程序名,還有其他的可以自行去看這個頭文件的定義。
日志調試
BPF提供一個bpf_trace_printk函數來打印調試信息,在android下,可以使用atrace命令來讀取。
并且我通過strace對atrace進行跟蹤發現,其實只需要執行下面兩句命令:
$ echo 1 > /sys/kernel/tracing/tracing_on
$ cat /sys/kernel/tracing/trace_pipe
我在想,通過這個調試信息,好想也能把BPF的數據傳送給用戶態的loader程序。
參考
- https://github.com/ehids/ecapture
- https://zhuanlan.zhihu.com/p/482266243
- https://github.com/omnirom/android_system_bpf/blob/0706429da9a9fb15d93d8ed8300af77410311a69/progs/include/bpf_helpers.h
- https://elixir.bootlin.com/linux/v5.10.150/source/tools/lib/bpf/libbpf.c#L8319
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/2003/