Extended Berkeley Packet Filter (eBPF) เป็นเครื่องเสมือนในเคอร์เนลที่รันโปรแกรม eBPF ที่ผู้ใช้จัดหาเพื่อขยายฟังก์ชันการทำงานของเคอร์เนล โปรแกรมเหล่านี้สามารถเชื่อมต่อกับโพรบหรือเหตุการณ์ในเคอร์เนล และใช้เพื่อรวบรวมสถิติเคอร์เนลที่มีประโยชน์ มอนิเตอร์ และดีบัก โปรแกรมถูกโหลดเข้าสู่เคอร์เนลโดยใช้ bpf(2)
syscall และจัดทำโดยผู้ใช้ในรูปแบบไบนารี่ของคำสั่งเครื่อง eBPF ระบบบิลด์ Android รองรับการคอมไพล์โปรแกรม C ไปยัง eBPF โดยใช้ไวยากรณ์ไฟล์บิลด์แบบง่ายที่อธิบายไว้ในเอกสารนี้
ข้อมูลเพิ่มเติมเกี่ยวกับสถาปัตยกรรมภายในและสถาปัตยกรรม eBPF สามารถดูได้ที่ หน้า eBPF ของ Brendan Gregg
Android มีตัวโหลดและไลบรารี eBPF ที่โหลดโปรแกรม eBPF ในเวลาบูต
ตัวโหลด Android BPF
ในระหว่างการบูต Android โปรแกรม eBPF ทั้งหมดที่อยู่ใน /system/etc/bpf/
จะถูกโหลด โปรแกรมเหล่านี้เป็นออบเจ็กต์ไบนารีที่สร้างโดยระบบบิลด์ Android จากโปรแกรม C และมาพร้อมกับไฟล์ Android.bp
ในแผนผังซอร์สของ Android ระบบ build เก็บอ็อบเจ็กต์ที่สร้างขึ้นไว้ที่ /system/etc/bpf
และอ็อบเจ็กต์เหล่านั้นจะกลายเป็นส่วนหนึ่งของอิมเมจระบบ
รูปแบบของโปรแกรม Android eBPF C
โปรแกรม 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 will also define 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
เป็นฟังก์ชันที่เมื่อคอมไพล์แล้วจะถูกวางไว้ในส่วนของไฟล์ผลลัพธ์
เคโพรบ | เชื่อมต่อ PROGFUNC เข้ากับคำสั่งเคอร์เนลโดยใช้โครงสร้างพื้นฐาน kprobe PROGNAME ต้องเป็นชื่อของฟังก์ชันเคอร์เนลที่กำลัง kprobed โปรดดู เอกสารประกอบเคอร์เนล kprobe สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ kprobes |
---|---|
จุดติดตาม | เชื่อมต่อ PROGFUNC เข้ากับจุดติดตาม PROGNAME ต้องอยู่ในรูปแบบ SUBSYSTEM/EVENT ตัวอย่างเช่น ส่วนจุดติดตามสำหรับการแนบฟังก์ชันกับเหตุการณ์การสลับบริบทของตัวกำหนดตารางเวลาจะเป็น SEC("tracepoint/sched/sched_switch") โดยที่ sched คือชื่อของระบบย่อยการติดตาม และ sched_switch คือชื่อของเหตุการณ์การติดตาม ตรวจสอบ เอกสารประกอบเคอร์เนลเหตุการณ์การติดตาม สำหรับข้อมูลเพิ่มเติมเกี่ยวกับจุดติดตาม |
สก์ฟิลเตอร์ | โปรแกรมทำหน้าที่เป็นตัวกรองซ็อกเก็ตเครือข่าย |
กำหนดการ | โปรแกรมทำหน้าที่เป็นตัวแยกประเภทการรับส่งข้อมูลเครือข่าย |
cgroupskb, cgroupsock | โปรแกรมทำงานทุกครั้งที่กระบวนการในกลุ่ม CG สร้างซ็อกเก็ต AF_INET หรือ AF_INET6 |
ประเภทเพิ่มเติมสามารถพบได้ใน ซอร์สโค้ดของ Loader
ตัวอย่างเช่น โปรแกรม myschedtp.c
ต่อไปนี้จะเพิ่มข้อมูลเกี่ยวกับ PID งานล่าสุดที่ทำงานบน CPU ตัวใดตัวหนึ่ง โปรแกรมนี้บรรลุเป้าหมายโดยการสร้างแผนที่และกำหนดฟังก์ชัน 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");
มาโคร LICENSE ใช้เพื่อตรวจสอบว่าโปรแกรมเข้ากันได้กับลิขสิทธิ์ของเคอร์เนลหรือไม่ เมื่อโปรแกรมใช้ฟังก์ชันตัวช่วย BPF ที่เคอร์เนลจัดเตรียมไว้ให้ ระบุชื่อใบอนุญาตโปรแกรมของคุณในรูปแบบสตริง เช่น LICENSE("GPL")
หรือ LICENSE("Apache 2.0")
รูปแบบของไฟล์ Android.bp
เพื่อให้ระบบ Android build สามารถสร้างโปรแกรม eBPF .c
คุณต้องสร้างรายการในไฟล์ Android.bp
ของโปรเจ็กต์ ตัวอย่างเช่น หากต้องการสร้างโปรแกรม eBPF C ชื่อ 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
ไลบรารี Android BPF มีชื่อว่า libbpf_android.so
และเป็นส่วนหนึ่งของอิมเมจระบบ ไลบรารีนี้มอบฟังก์ชัน eBPF ระดับต่ำให้กับผู้ใช้ซึ่งจำเป็นสำหรับการสร้างและอ่านแผนที่ การสร้างโพรบ จุดติดตาม และบัฟเฟอร์ที่สมบูรณ์แบบ
การแนบโปรแกรมเข้ากับจุดติดตาม
โปรแกรม Tracepoint จะถูกโหลดโดยอัตโนมัติเมื่อบูต หลังจากโหลดแล้ว ต้องเปิดใช้งานโปรแกรมจุดติดตามโดยใช้ขั้นตอนเหล่านี้:
- โทร
bpf_obj_get()
เพื่อรับโปรแกรมfd
จากตำแหน่งของไฟล์ที่ปักหมุด สำหรับข้อมูลเพิ่มเติม โปรดดู ไฟล์ที่มีอยู่ใน sysfs - เรียก
bpf_attach_tracepoint()
ในไลบรารี BPF โดยส่งผ่านโปรแกรมfd
และชื่อจุดติดตาม
ตัวอย่างโค้ดต่อไปนี้แสดงวิธีแนบจุดติดตาม sched_switch
ที่กำหนดไว้ในไฟล์ต้นฉบับ 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:
โปรแกรม
netd
eBPF C ถูกใช้โดย networking daemon (netd) ใน Android เพื่อวัตถุประสงค์ต่างๆ เช่น การกรองซ็อกเก็ต และการรวบรวมสถิติ หากต้องการดูวิธีการใช้โปรแกรมนี้ ให้ตรวจสอบแหล่งที่มาของ การตรวจสอบการรับส่งข้อมูล eBPFโปรแกรม
time_in_state
eBPF C คำนวณระยะเวลาที่แอป Android ใช้ที่ความถี่ CPU ที่แตกต่างกัน ซึ่งใช้ในการคำนวณพลังงานใน Android 12 โปรแกรม
gpu_mem
eBPF C จะติดตามการใช้งานหน่วยความจำ GPU ทั้งหมดสำหรับแต่ละกระบวนการและสำหรับทั้งระบบ โปรแกรมนี้ใช้สำหรับการทำโปรไฟล์หน่วยความจำ GPU