Extended Berkeley Packet Filter (eBPF) היא מכונה וירטואלית בתוך הליבה שמריצה תוכניות eBPF שסופקו על ידי המשתמש כדי להרחיב את הפונקציונליות של הליבה. אפשר לקשר את התוכניות האלה לבדיקות או לאירועים בקרנל, ולהשתמש בהן כדי לאסוף נתונים סטטיסטיים שימושיים של הקרנל, לעקוב אחרי הקרנל ולנפות בו באגים. תוכנה נטענת לליבה באמצעות קריאת המערכת bpf(2), והמשתמש מספק אותה כ-blob בינארי של הוראות מכונה eBPF. מערכת ה-build של Android תומכת בהידור של תוכניות C ל-eBPF באמצעות תחביר פשוט של קובץ build שמתואר במסמך הזה.
מידע נוסף על המבנה הפנימי והארכיטקטורה של eBPF זמין בדף eBPF של ברנדן גרג.
Android כולל טוען eBPF וספרייה שטוענים תוכניות eBPF בזמן האתחול.
Android BPF loader
במהלך האתחול של Android, כל תוכניות ה-eBPF שנמצאות ב-/system/etc/bpf/ נטענות. התוכניות האלה הן אובייקטים בינאריים שנבנו על ידי מערכת ה-Build של Android מתוכניות C, והן מלוות בקובצי Android.bp בעץ המקור של Android. מערכת ה-build מאחסנת את האובייקטים שנוצרו ב-/system/etc/bpf, והאובייקטים האלה הופכים לחלק מתמונת המערכת.
הפורמט של תוכנת C ב-eBPF ל-Android
תוכנית eBPF C צריכה להיות בפורמט הבא:
#include <bpf_helpers.h>
/* Define one or more maps in the maps section, for example
* define a map of type array int -> uint32_t, with 10 entries
*/
DEFINE_BPF_MAP(name_of_my_map, ARRAY, int, uint32_t, 10);
/* this also defines type-safe accessors:
* value * bpf_name_of_my_map_lookup_elem(&key);
* int bpf_name_of_my_map_update_elem(&key, &value, flags);
* int bpf_name_of_my_map_delete_elem(&key);
* as such it is heavily suggested to use lowercase *_map names.
* Also note that due to compiler deficiencies you cannot use a type
* of 'struct foo' but must instead use just 'foo'. As such structs
* must not be defined as 'struct foo {}' and must instead be
* 'typedef struct {} foo'.
*/
DEFINE_BPF_PROG("PROGTYPE/PROGNAME", AID_*, AID_*, PROGFUNC)(..args..) {
<body-of-code
... read or write to MY_MAPNAME
... do other things
>
}
LICENSE("GPL"); // or other licenseאיפה:
-
name_of_my_mapהוא השם של משתנה המפה. השם הזה מודיע לטוען ה-BPF על סוג המפה שצריך ליצור ועל הפרמטרים שבהם צריך להשתמש. הגדרת המבנה הזו מסופקת על ידי הכותרתbpf_helpers.hשנכללת.
PROGTYPE/PROGNAMEמייצג את סוג התוכנית ואת שם התוכנית. סוג התוכנית יכול להיות כל אחת מהאפשרויות שמפורטות בטבלה הבאה. אם סוג התוכנית לא מופיע ברשימה, אין מוסכמת שמות מחייבת לתוכנית. השם צריך להיות מוכר רק לתהליך שמצרף את התוכנית.
PROGFUNCהיא פונקציה שמוצבת, לאחר ההידור, בקטע של הקובץ שנוצר.
| kprobe | Hooks PROGFUNC onto at a kernel instruction using the
kprobe infrastructure. PROGNAME חייב להיות השם של פונקציית הליבה שנבדקת באמצעות kprobe. מידע נוסף על kprobes מופיע במסמכי העזרה של ליבת kprobe.
|
|---|---|
| נקודת מעקב | Hooks PROGFUNC אל נקודת מעקב. PROGNAME חייב להיות בפורמט SUBSYSTEM/EVENT. לדוגמה, קטע של נקודת מעקב לצירוף פונקציות לאירועי החלפת הקשר של המתזמן יהיה SEC("tracepoint/sched/sched_switch"), כאשר sched הוא השם של מערכת המשנה של המעקב ו-sched_switch הוא השם של אירוע המעקב. מידע נוסף על נקודות מעקב זמין במסמכי התיעוד בנושא אירועי מעקב של ליבת המערכת.
|
| skfilter | פונקציות תוכנית כמסנן שקע ברשת. |
| schedcls | פונקציות תוכנה ככלי לסיווג תנועת גולשים ברשת. |
| cgroupskb, cgroupsock | התוכנית פועלת בכל פעם שתהליכים ב-CGroup יוצרים שקע AF_INET או AF_INET6. |
סוגים נוספים מפורטים בקוד המקור של Loader.
לדוגמה, התוכנית myschedtp.c הבאה מוסיפה מידע על מזהה התהליך (PID) של המשימה האחרונה שהופעלה במעבד מסוים. התוכנית הזו משיגה את המטרה שלה על ידי יצירת מפה והגדרת tp_sched_switchפונקציה שאפשר לצרף לsched:sched_switchאירוע המעקב. מידע נוסף זמין במאמר בנושא צירוף תוכניות לנקודות מעקב.
#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; }; DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_SYSTEM, 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 1; // return 1 to avoid blocking simpleperf from receiving events } LICENSE("GPL");
מאקרו הרישיון משמש לאימות התאימות של התוכנית לרישיון של ליבת מערכת ההפעלה, כשהתוכנית משתמשת בפונקציות העזר של BPF שסופקו על ידי הליבה. מציינים את שם הרישיון של התוכנית בפורמט מחרוזת, כמו
LICENSE("GPL") או LICENSE("Apache 2.0").
הפורמט של קובץ Android.bp
כדי שמערכת ה-build של Android תיצור תוכנית eBPF .c, צריך ליצור רשומה בקובץ Android.bp של הפרויקט. לדוגמה, כדי ליצור תוכנית C של eBPF בשם bpf_test.c, מוסיפים את הרשומה הבאה לקובץ Android.bp של הפרויקט:
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}הרשומה הזו כוללת את תוכנית C שנוצרה מהאובייקט /system/etc/bpf/bpf_test.o. בזמן האתחול, מערכת Android טוענת אוטומטית את התוכנית bpf_test.o לליבת המערכת.
קבצים שזמינים ב-sysfs
במהלך האתחול, מערכת Android טוענת באופן אוטומטי את כל אובייקטי ה-eBPF מ-/system/etc/bpf/, יוצרת את המיפויים שהתוכנית צריכה ומצמידה את התוכנית הטעונה עם המיפויים שלה למערכת הקבצים של BPF. אחר כך אפשר להשתמש בקבצים האלה כדי ליצור אינטראקציה נוספת עם תוכנית ה-eBPF או כדי לקרוא מפות. בקטע הזה מתואמות המוסכמות שמשמשות למתן שמות לקבצים האלה ולמיקומים שלהם ב-sysfs.
הקבצים הבאים נוצרים ומוצמדים:
לכל תוכנית שנטענה, בהנחה ש-
PROGNAMEהוא שם התוכנית ו-FILENAMEהוא שם קובץ ה-eBPF C, טוען Android יוצר ומצמיד כל תוכנית ב-/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.לדוגמה, בדוגמה הקודמת של נקודת מעקב
sched_switchב-myschedtp.c, נוצר קובץ תוכנית שמוצמד ל-/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.לכל מפה שנוצרת, בהנחה ש-
MAPNAMEהוא שם המפה ו-FILENAMEהוא שם קובץ ה-eBPF C, טוען Android יוצר ומצמיד כל מפה ל-/sys/fs/bpf/map_FILENAME_MAPNAME.לדוגמה, בדוגמה הקודמת של נקודת מעקב
sched_switchב-myschedtp.c, נוצר קובץ מפה והוא מוצמד ל-/sys/fs/bpf/map_myschedtp_cpu_pid_map.
bpf_obj_get()בספריית Android BPF מחזירה מתאר קובץ מהקובץ/sys/fs/bpfהמוצמד. אפשר להשתמש בתיאור הקובץ הזה לפעולות נוספות, כמו קריאת מפות או צירוף תוכנית לנקודת מעקב.
ספריית Android BPF
ספריית ה-BPF של Android נקראת libbpf_android.so והיא חלק מתמונת המערכת. הספרייה הזו מספקת למשתמש יכולות eBPF ברמה נמוכה שנדרשות ליצירה ולקריאה של מפות, ליצירה של בדיקות, נקודות מעקב ומאגרי perf.
צירוף תוכניות לנקודות מעקב
תוכניות של נקודות מעקב נטענות אוטומטית בזמן האתחול. אחרי הטעינה, צריך להפעיל את תוכנית נקודת המעקב באמצעות השלבים הבאים:
- מתקשרים אל
bpf_obj_get()כדי לקבל את התוכניתfdמהמיקום של הקובץ המוצמד. מידע נוסף זמין במאמר בנושא קבצים שזמינים ב-sysfs. - מתקשרים אל
bpf_attach_tracepoint()בספריית ה-BPF, מעבירים אליו את התוכניתfdואת השם של נקודת המעקב.
בדוגמה הבאה של קוד אפשר לראות איך לצרף את sched_switch tracepoint
שמוגדר בקובץ המקור myschedtp.c הקודם (לא מוצגות בדיקות שגיאות):
char *tp_prog_path = "/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch"; char *tp_map_path = "/sys/fs/bpf/map_myschedtp_cpu_pid"; // Attach tracepoint and wait for 4 seconds int mProgFd = bpf_obj_get(tp_prog_path); int mMapFd = bpf_obj_get(tp_map_path); int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch"); sleep(4); // 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));
קריאה מהמפות
מיפוי BPF תומך במבנים או בסוגים שרירותיים ומורכבים של מפתחות וערכים. ספריית Android BPF כוללת מחלקה android::BpfMap שמשתמשת בתבניות C++ כדי ליצור מופע של BpfMap על סמך סוג המפתח והערך של המיפוי הרלוונטי. בדוגמת הקוד הקודמת השתמשנו ב-BpfMap עם מפתח וערך כמספרים שלמים. המספרים השלמים יכולים להיות גם מבנים שרירותיים.
לכן, המחלקה BpfMap שנוצרת באמצעות תבנית מאפשרת להגדיר אובייקט BpfMap בהתאמה אישית שמתאים למפה הספציפית. אחר כך אפשר לגשת למפה באמצעות הפונקציות שנוצרו בהתאמה אישית, שהן מודעות לסוגים, וכך מתקבל קוד נקי יותר.
מידע נוסף על BpfMap זמין במקורות של Android.
לנפות באגים
במהלך אתחול המערכת, נרשמים ביומן כמה הודעות שקשורות לטעינת BPF. אם תהליך הטעינה נכשל מסיבה כלשהי, מוצגת הודעת יומן מפורטת ב-logcat. סינון של יומני logcat לפי bpf מדפיס את כל ההודעות ואת כל השגיאות המפורטות במהלך זמן הטעינה, כמו שגיאות של כלי האימות eBPF.
דוגמאות לשימוש ב-eBPF ב-Android
התוכניות הבאות ב-AOSP מספקות דוגמאות נוספות לשימוש ב-eBPF:
הדימון (daemon) של הרשת (netd) ב-Android משתמש ב
netdתוכנית eBPF C למטרות שונות, כמו סינון שקעים ואיסוף נתונים סטטיסטיים. כדי לראות איך משתמשים בתוכנית הזו, אפשר לעיין במקורות של כלי המעקב אחר תנועה של eBPF.time_in_stateתוכנית eBPF C מחשבת את משך הזמן שבו אפליקציית Android פועלת בתדרים שונים של המעבד, ומשתמשת בנתונים האלה כדי לחשב את צריכת החשמל.ב-Android בגרסה 12,
gpu_memתוכנית eBPF C עוקבת אחרי השימוש הכולל בזיכרון ה-GPU לכל תהליך ולמערכת כולה. התוכנית הזו משמשת ליצירת פרופיל של זיכרון ה-GPU.