Memperluas Kernel dengan eBPF

Extended Berkeley Packet Filter (eBPF) adalah mesin virtual dalam kernel yang menjalankan program eBPF yang disediakan pengguna untuk memperluas fungsionalitas kernel. Program-program ini dapat dihubungkan ke probe atau peristiwa di kernel dan digunakan untuk mengumpulkan statistik kernel yang berguna, memantau, dan melakukan debug. Sebuah program dimuat ke dalam kernel menggunakan syscall bpf(2) dan disediakan oleh pengguna sebagai gumpalan biner instruksi mesin eBPF. Sistem build Android memiliki dukungan untuk mengompilasi program C ke eBPF menggunakan sintaks file build sederhana yang dijelaskan dalam dokumen ini.

Informasi lebih lanjut tentang internal dan arsitektur eBPF dapat ditemukan di halaman eBPF Brendan Gregg .

Android menyertakan pemuat dan pustaka eBPF yang memuat program eBPF saat boot.

Pemuat BPF Android

Selama boot Android, semua program eBPF yang terletak di /system/etc/bpf/ dimuat. Program ini adalah objek biner yang dibangun oleh sistem build Android dari program C dan disertai dengan file Android.bp di pohon sumber Android. Sistem build menyimpan objek yang dihasilkan di /system/etc/bpf , dan objek tersebut menjadi bagian dari image sistem.

Format program Android eBPF C

Program eBPF C harus memiliki format berikut:

#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

Di mana:

  • name_of_my_map adalah nama variabel peta Anda. Nama ini memberi tahu pemuat BPF tentang jenis peta yang akan dibuat dan dengan parameter apa. Definisi struct ini disediakan oleh header bpf_helpers.h yang disertakan.
  • PROGTYPE/PROGNAME mewakili jenis program dan nama program. Jenis programnya bisa berupa apa saja yang tercantum dalam tabel berikut. Jika suatu jenis program tidak terdaftar, tidak ada konvensi penamaan yang ketat untuk program tersebut; namanya hanya perlu diketahui proses yang melekatkan program tersebut.

  • PROGFUNC adalah fungsi yang, ketika dikompilasi, ditempatkan di bagian file yang dihasilkan.

penyelidikan Kaitkan PROGFUNC ke instruksi kernel menggunakan infrastruktur kprobe. PROGNAME harus berupa nama fungsi kernel yang diperiksa. Lihat dokumentasi kernel kprobe untuk informasi lebih lanjut tentang kprobe.
titik jejak Kaitkan PROGFUNC ke titik jejak. PROGNAME harus dalam format SUBSYSTEM/EVENT . Misalnya, bagian tracepoint untuk melampirkan fungsi ke peristiwa peralihan konteks penjadwal adalah SEC("tracepoint/sched/sched_switch") , dengan sched adalah nama subsistem pelacakan, dan sched_switch adalah nama peristiwa pelacakan. Periksa dokumentasi kernel peristiwa pelacakan untuk informasi lebih lanjut tentang titik jejak.
filtersk Program berfungsi sebagai filter soket jaringan.
jadwal Program berfungsi sebagai pengklasifikasi lalu lintas jaringan.
cgroupskb, cgroupsock Program berjalan setiap kali proses dalam CGroup membuat soket AF_INET atau AF_INET6.

Tipe tambahan dapat ditemukan di kode sumber Loader .

Misalnya, program myschedtp.c berikut menambahkan informasi tentang PID tugas terbaru yang dijalankan pada CPU tertentu. Program ini mencapai tujuannya dengan membuat peta dan mendefinisikan fungsi tp_sched_switch yang dapat dilampirkan ke peristiwa jejak sched:sched_switch . Untuk informasi lebih lanjut, lihat Melampirkan program ke titik jejak .

#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");

Makro LICENSE digunakan untuk memverifikasi apakah program kompatibel dengan lisensi kernel ketika program menggunakan fungsi pembantu BPF yang disediakan oleh kernel. Tentukan nama lisensi program Anda dalam bentuk string, seperti LICENSE("GPL") atau LICENSE("Apache 2.0") .

Format file Android.bp

Agar sistem build Android dapat membangun program eBPF .c , Anda harus membuat entri di file Android.bp proyek tersebut. Misalnya, untuk membuat program eBPF C bernama bpf_test.c , buat entri berikut di file Android.bp proyek Anda:

bpf {
    name: "bpf_test.o",
    srcs: ["bpf_test.c"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
}

Entri ini mengkompilasi program C yang menghasilkan objek /system/etc/bpf/bpf_test.o . Saat boot, sistem Android secara otomatis memuat program bpf_test.o ke dalam kernel.

File tersedia di sysfs

Selama boot, sistem Android secara otomatis memuat semua objek eBPF dari /system/etc/bpf/ , membuat peta yang dibutuhkan program, dan menyematkan program yang dimuat beserta petanya ke sistem file BPF. File-file ini kemudian dapat digunakan untuk interaksi lebih lanjut dengan program eBPF atau membaca peta. Bagian ini menjelaskan konvensi yang digunakan untuk memberi nama file-file ini dan lokasinya di sysfs.

File berikut dibuat dan disematkan:

  • Untuk setiap program yang dimuat, dengan asumsi PROGNAME adalah nama program dan FILENAME adalah nama file eBPF C, pemuat Android akan membuat dan menyematkan setiap program di /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME .

    Misalnya, untuk contoh tracepoint sched_switch sebelumnya di myschedtp.c , file program dibuat dan disematkan ke /sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch .

  • Untuk setiap peta yang dibuat, dengan asumsi MAPNAME adalah nama peta dan FILENAME adalah nama file eBPF C, pemuat Android akan membuat dan menyematkan setiap peta ke /sys/fs/bpf/map_FILENAME_MAPNAME .

    Misalnya, untuk contoh tracepoint sched_switch sebelumnya di myschedtp.c , file peta dibuat dan disematkan ke /sys/fs/bpf/map_myschedtp_cpu_pid_map .

  • bpf_obj_get() di perpustakaan Android BPF mengembalikan deskriptor file dari file /sys/fs/bpf yang disematkan. Deskriptor file ini dapat digunakan untuk operasi lebih lanjut, seperti membaca peta atau melampirkan program ke tracepoint.

Perpustakaan BPF Android

Pustaka Android BPF diberi nama libbpf_android.so dan merupakan bagian dari citra sistem. Pustaka ini memberi pengguna fungsionalitas eBPF tingkat rendah yang diperlukan untuk membuat dan membaca peta, membuat probe, titik jejak, dan buffer kinerja.

Melampirkan program ke tracepoint

Program Tracepoint dimuat secara otomatis saat boot. Setelah memuat, program tracepoint harus diaktifkan menggunakan langkah-langkah berikut:

  1. Panggil bpf_obj_get() untuk mendapatkan program fd dari lokasi file yang disematkan. Untuk informasi lebih lanjut, lihat File yang tersedia di sysfs .
  2. Panggil bpf_attach_tracepoint() di perpustakaan BPF, teruskan program fd dan nama tracepoint ke dalamnya.

Contoh kode berikut menunjukkan cara melampirkan tracepoint sched_switch yang ditentukan dalam file sumber myschedtp.c sebelumnya (pemeriksaan kesalahan tidak ditampilkan):

  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));

Membaca dari peta

Peta BPF mendukung struktur atau tipe kunci dan nilai kompleks yang berubah-ubah. Library Android BPF menyertakan class android::BpfMap yang menggunakan template C++ untuk membuat instance BpfMap berdasarkan tipe kunci dan nilai untuk peta yang dimaksud. Contoh kode sebelumnya menunjukkan penggunaan BpfMap dengan kunci dan nilai sebagai bilangan bulat. Bilangan bulat juga dapat berupa struktur sembarang.

Dengan demikian, kelas BpfMap yang ditemplat memudahkan untuk menentukan objek BpfMap khusus yang sesuai untuk peta tertentu. Peta kemudian dapat diakses menggunakan fungsi yang dibuat khusus, yang peka terhadap tipe, sehingga menghasilkan kode yang lebih bersih.

Untuk informasi lebih lanjut tentang BpfMap , lihat sumber Android .

Masalah debug

Selama waktu boot, beberapa pesan terkait pemuatan BPF dicatat. Jika proses pemuatan gagal karena alasan apa pun, pesan log terperinci disediakan di logcat. Memfilter log logcat menurut "bpf" akan mencetak semua pesan dan kesalahan detail apa pun selama waktu muat, seperti kesalahan pemverifikasi eBPF.

Contoh eBPF di Android

Program berikut di AOSP memberikan contoh tambahan penggunaan eBPF:

  • Program netd eBPF C digunakan oleh daemon jaringan (netd) di Android untuk berbagai tujuan seperti pemfilteran soket dan pengumpulan statistik. Untuk melihat bagaimana program ini digunakan, periksa sumber pemantau lalu lintas eBPF .

  • Program time_in_state eBPF C menghitung jumlah waktu yang dihabiskan aplikasi Android pada frekuensi CPU berbeda, yang digunakan untuk menghitung daya.

  • Di Android 12, program gpu_mem eBPF C melacak total penggunaan memori GPU untuk setiap proses dan keseluruhan sistem. Program ini digunakan untuk pembuatan profil memori GPU.