Extended Berkeley Packet Filter (eBPF) è una macchina virtuale in-kernel che
esegue programmi eBPF forniti dall'utente per estendere la funzionalità del kernel. Questi programmi
può essere agganciato a probe o eventi nel kernel e utilizzato per raccogliere
statistiche, monitoraggio e debug del kernel. Un programma è
caricato nel kernel mediante la chiamata di sistema bpf(2)
ed è fornito dall'utente
come blob binario delle istruzioni della macchina eBPF. Il sistema di compilazione di Android
assistenza per la compilazione
in eBPF utilizzando la semplice sintassi del file di build descritta in questo documento.
Ulteriori informazioni sugli aspetti interni e sull'architettura di eBPF sono disponibili nella pagina di Brendan Gregg su eBPF.
Android include un caricatore e una libreria eBPF che caricano i programmi eBPF al momento dell'avvio.
Caricatore BPF Android
Durante l'avvio di Android, tutti i programmi eBPF all'indirizzo /system/etc/bpf/
vengono
caricato. Questi programmi sono oggetti binari creati dal sistema di build di Android
da programmi C e sono accompagnati da file Android.bp
nell'albero di origine di Android. Il sistema di compilazione archivia gli oggetti generati in /system/etc/bpf
e
diventano parte dell'immagine di sistema.
Formato di un programma C eBPF per Android
Un programma eBPF C deve avere il seguente formato:
#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
Dove:
name_of_my_map
è il nome della variabile mappa. Questo comunica al caricatore BPF il tipo di mappa da creare e parametri. Questa definizione di struct è fornita dall'intestazionebpf_helpers.h
inclusa.PROGTYPE/PROGNAME
rappresenta il tipo di programma e il nome del programma. Il tipo di programma può essere uno di quelli elencati nel seguente. Quando un tipo di programma non è elencato, non esiste un nome rigoroso convenzione per il programma; il nome deve essere noto al processo che allega il programma.PROGFUNC
è una funzione che, una volta compilata, viene inserita in una sezione del file risultante.
Kprobe | Esegue il hooking PROGFUNC su un'istruzione del kernel utilizzando l'infrastruttura kprobe. PROGNAME deve essere il nome del kernel
di una funzione kprobed. Per ulteriori informazioni sui kprobe, consulta la documentazione del kernel kprobe.
|
---|---|
tracepoint | Collega PROGFUNC a un punto traccia. PROGNAME deve essere
nel formato SUBSYSTEM/EVENT . Ad esempio, una sezione tracepoint
per il collegamento di funzioni agli eventi di cambio
contesto dello scheduler sarebbe
SEC("tracepoint/sched/sched_switch") , dove sched è
il nome del sottosistema traccia, mentre sched_switch è il nome
dell'evento traccia. Controlla il kernel degli eventi di traccia.
documentazione per ulteriori informazioni sui tracepoint.
|
filtro sk | Il programma funziona come filtro socket di rete. |
piani | Il programma funziona come classificatore del traffico di rete. |
cgroupskb, cgroupsock | Il programma viene eseguito ogni volta che i processi in un gruppo C creano un AF_INET o AF_INET6 presa. |
Altri tipi sono disponibili nel codice sorgente del caricatore.
Ad esempio, il seguente programma myschedtp.c
aggiunge informazioni su
il PID più recente dell'attività eseguito su una particolare CPU. Questo programma raggiunge il suo obiettivo
creando una mappa e definendo una funzione tp_sched_switch
che può essere
collegata all'evento di traccia sched:sched_switch
. Per ulteriori informazioni, consulta
Collegare i programmi ai tracepoint.
#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");
La macro LICENSE viene utilizzata per verificare se il programma è compatibile con la licenza del kernel quando utilizza le funzioni di assistenza BPF fornite dal kernel. Specifica il nome della licenza del programma in formato stringa, ad esempio
LICENSE("GPL")
o LICENSE("Apache 2.0")
.
Formato del file Android.bp
Affinché il sistema di compilazione Android possa compilare un programma .c
eBPF, devi creare una voce nel file Android.bp
del progetto. Ad esempio, per compilare un programma C eBPF denominato bpf_test.c
, inserisci la seguente voce nel file Android.bp
del progetto:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
Questa voce compila il programma C, generando l'oggetto
/system/etc/bpf/bpf_test.o
. All'avvio, il sistema Android carica automaticamente
il programma bpf_test.o
nel kernel.
File disponibili in sysfs
Durante l'avvio, il sistema Android carica automaticamente tutti gli oggetti eBPF da/system/etc/bpf/
, crea le mappe necessarie per il programma e blocca il programma caricato con le relative mappe nel file system BPF. Questi file possono essere utilizzati per dialogare ulteriormente con il programma eBPF o per leggere le mappe. Questa sezione
descrive le convenzioni utilizzate per denominare questi file e le relative posizioni in
sysfs.
I seguenti file vengono creati e fissati:
Per tutti i programmi caricati, presupponendo che
PROGNAME
sia il nome del programma eFILENAME
è il nome del file eBPF C, il caricatore Android crea e fissa ogni programma su/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.Ad esempio, per l'esempio precedente di tracepoint
sched_switch
inmyschedtp.c
, un file di programma viene creato e bloccato su/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
.Per tutte le mappe create, supponendo che
MAPNAME
sia il nome della mappa eFILENAME
sia il nome del file C eBPF, il caricatore Android crea e blocca ogni mappa su/sys/fs/bpf/map_FILENAME_MAPNAME
.Ad esempio, per l'esempio precedente del tracepoint
sched_switch
inmyschedtp.c
, viene creato un file mappa e fissato su/sys/fs/bpf/map_myschedtp_cpu_pid_map
.bpf_obj_get()
nella libreria BPF di Android restituisce un descrittore di file dal metodo/sys/fs/bpf
file fissato. Questo descrittore file può essere utilizzato per ulteriori operazioni, come la lettura delle mappe o l'associazione di un programma a un punto traccia.
Libreria BPF per Android
La libreria Android BPF è denominata libbpf_android.so
e fa parte del sistema
dell'immagine. Questa libreria fornisce all'utente le funzionalità eBPF di basso livello necessarie
per creare e leggere mappe, creare probe, tracepoint e buffer di prestazioni.
Collega programmi ai tracepoint
I programmi Tracepoint vengono caricati automaticamente all'avvio. Dopo il caricamento, il programma dei punti di traccia deve essere attivato seguendo questa procedura:
- Chiama
bpf_obj_get()
per ottenere il programmafd
dal file in ogni località. Per ulteriori informazioni, consulta File disponibili in sysfs. - Chiama
bpf_attach_tracepoint()
nella libreria BPF, passando il programmafd
e il nome del punto di traccia.
Il seguente esempio di codice mostra come collegare il punto di traccia sched_switch
definito nel file di origine myschedtp.c
precedente (il controllo degli errori non viene mostrato):
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));
Leggi dalle mappe
Le mappe BPF supportano tipi o strutture di chiavi e valori complessi arbitrari. La
libreria BPF di Android include una classe android::BpfMap
che utilizza i modelli C++ per creare istanze di BpfMap
in base al tipo di chiave e valore per la mappa in questione. L'esempio di codice precedente mostra l'utilizzo di un BpfMap
con chiave
e valore come numeri interi. Gli interi possono anche essere strutture arbitrarie.
Pertanto, la classe BpfMap
basata su modelli consente di definire un oggetto BpfMap
personalizzato adatto alla mappa specifica. È quindi possibile accedere alla mappa utilizzando
funzioni generate in modo personalizzato, sensibili al tipo, e generare un codice più pulito.
Per ulteriori informazioni su BpfMap
, consulta le origini Android.
Eseguire il debug dei problemi
Durante il tempo di avvio vengono registrati diversi messaggi relativi al caricamento di BPF. Se il processo di caricamento non va a buon fine per qualsiasi motivo, in logcat viene fornito un messaggio dettagliato del log. Se filtri i log di logcat per bpf
, vengono stampati tutti i messaggi e eventuali errori dettagliati durante il tempo di caricamento, ad esempio gli errori del verificatore eBPF.
Esempi di eBPF in Android
I seguenti programmi in AOSP forniscono ulteriori esempi di utilizzo di eBPF:
netd
eBPF C programma viene utilizzato dal daemon di rete (netd) in Android per vari scopi, come come filtro dei socket e raccolta di statistiche. Per vedere come viene usato questo programma: controlla il traffico eBPF di monitoraggio.time_in_state
eBPF C programma calcola la quantità di tempo di permanenza di un'app Android in diversi Frequenze della CPU, utilizzate per calcolare la potenza.In Android 12, il
gpu_mem
eBPF C programma tiene traccia dell'utilizzo totale della memoria GPU per ogni processo e per l'intero sistema. Questo programma viene utilizzato per il profiling della memoria GPU.