close

作者:Hcamael@知道創宇404實驗室日期:2022年11月4日

最近在研究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程序了嗎?接着,就開啟了我的研究。




AndroidBPF 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


作者名片


END



往期熱門
(點擊圖片跳轉)

戳「閱讀原文」更多精彩內容!
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()