Extended Berkeley Packet Filter (eBPF) est une machine virtuelle dans le noyau qui exécute des programmes eBPF fournis par l'utilisateur pour étendre les fonctionnalités du noyau. Ces programmes peuvent être associés à des sondes ou à des événements dans le noyau, et utilisés pour collecter des statistiques utiles sur le noyau, pour la surveillance et le débogage. Un programme est chargé dans le noyau à l'aide de l'appel système bpf(2) et est fourni par l'utilisateur sous la forme d'un blob binaire d'instructions machine eBPF. Le système de compilation Android permet de compiler des programmes C en eBPF à l'aide d'une syntaxe de fichier de compilation simple décrite dans ce document.
Pour en savoir plus sur le fonctionnement interne et l'architecture d'eBPF, consultez la page eBPF de Brendan Gregg.
Android inclut un chargeur et une bibliothèque eBPF qui chargent les programmes eBPF au moment du démarrage.
Chargeur BPF Android
Lors du démarrage d'Android, tous les programmes eBPF situés à l'emplacement /system/etc/bpf/ sont chargés. Ces programmes sont des objets binaires créés par le système de compilation Android à partir de programmes C. Ils sont accompagnés de fichiers Android.bp dans l'arborescence source Android. Le système de compilation stocke les objets générés dans /system/etc/bpf, et ces objets font partie de l'image système.
Format d'un programme C eBPF Android
Un programme C eBPF doit respecter le format suivant :
#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 licenseOù :
name_of_my_mapest le nom de votre variable de carte. Ce nom informe le chargeur BPF du type de carte à créer et des paramètres à utiliser. Cette définition de struct est fournie par l'en-têtebpf_helpers.hinclus.PROGTYPE/PROGNAMEreprésente le type et le nom du programme. Le type de programme peut être l'un de ceux listés dans le tableau ci-dessous. Lorsqu'un type de programme n'est pas listé, il n'existe pas de convention de dénomination stricte pour le programme. Le nom doit simplement être connu du processus qui associe le programme.PROGFUNCest une fonction qui, une fois compilée, est placée dans une section du fichier résultant.
| kprobe | Les hooks PROGFUNC sont associés à une instruction du noyau à l'aide de l'infrastructure kprobe. PROGNAME doit être le nom de la fonction du noyau faisant l'objet d'un kprobe. Pour en savoir plus sur les kprobes, consultez la documentation du noyau kprobe.
|
|---|---|
| point de trace | PROGFUNC s'accroche à un point de trace. PROGNAME doit être au format SUBSYSTEM/EVENT. Par exemple, une section de point de trace pour associer des fonctions à des événements de changement de contexte du planificateur serait SEC("tracepoint/sched/sched_switch"), où sched est le nom du sous-système de trace et sched_switch est le nom de l'événement de trace. Pour en savoir plus sur les tracepoints, consultez la documentation sur le noyau des événements de trace.
|
| skfilter | Le programme fonctionne comme un filtre de socket réseau. |
| schedcls | Le programme fonctionne comme un classificateur de trafic réseau. |
| cgroupskb, cgroupsock | Le programme s'exécute chaque fois que des processus d'un CGroup créent un socket AF_INET ou AF_INET6. |
D'autres types sont disponibles dans le code source du chargeur.
Par exemple, le programme myschedtp.c suivant ajoute des informations sur le dernier PID de tâche exécuté sur un processeur spécifique. Ce programme atteint son objectif en créant une carte et en définissant une fonction tp_sched_switch qui peut être associée à l'événement de trace sched:sched_switch. Pour en savoir plus, consultez Associer des programmes à des points de trace.
#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 permet de vérifier si le programme est compatible avec la licence du noyau lorsque le programme utilise des fonctions d'assistance BPF fournies par le noyau. Spécifiez le nom de la licence de votre programme sous forme de chaîne, par exemple LICENSE("GPL") ou LICENSE("Apache 2.0").
Format du fichier Android.bp
Pour que le système de compilation Android puisse compiler un programme .c eBPF, vous devez créer une entrée dans le fichier Android.bp du projet. Par exemple, pour compiler un programme C eBPF nommé bpf_test.c, ajoutez l'entrée suivante dans le fichier Android.bp de votre projet :
bpf {
name: "bpf_test.o",
srcs: ["bpf_test.c"],
cflags: [
"-Wall",
"-Werror",
],
}Cette entrée compile le programme C, ce qui génère l'objet /system/etc/bpf/bpf_test.o. Au démarrage, le système Android charge automatiquement le programme bpf_test.o dans le noyau.
Fichiers disponibles dans sysfs
Au démarrage, le système Android charge automatiquement tous les objets eBPF à partir de /system/etc/bpf/, crée les cartes dont le programme a besoin et épingle le programme chargé avec ses cartes au système de fichiers BPF. Ces fichiers peuvent ensuite être utilisés pour interagir davantage avec le programme eBPF ou lire des cartes. Cette section décrit les conventions utilisées pour nommer ces fichiers et leurs emplacements dans sysfs.
Les fichiers suivants sont créés et épinglés :
Pour tous les programmes chargés, en supposant que
PROGNAMEsoit le nom du programme etFILENAMEle nom du fichier C eBPF, le chargeur Android crée et épingle chaque programme à/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME.Par exemple, pour l'exemple de point de trace
sched_switchprécédent dansmyschedtp.c, un fichier de programme est créé et épinglé à/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch.Pour chaque carte créée, en supposant que
MAPNAMEsoit le nom de la carte etFILENAMEle nom du fichier C eBPF, le chargeur Android crée et épingle chaque carte à/sys/fs/bpf/map_FILENAME_MAPNAME.Par exemple, pour l'exemple de point de trace
sched_switchprécédent dansmyschedtp.c, un fichier de mappage est créé et épinglé à/sys/fs/bpf/map_myschedtp_cpu_pid_map.bpf_obj_get()dans la bibliothèque BPF Android renvoie un descripteur de fichier à partir du fichier/sys/fs/bpfépinglé. Ce descripteur de fichier peut être utilisé pour d'autres opérations, comme la lecture de cartes ou l'association d'un programme à un point de trace.
Bibliothèque Android BPF
La bibliothèque Android BPF est nommée libbpf_android.so et fait partie de l'image système. Cette bibliothèque fournit à l'utilisateur les fonctionnalités eBPF de bas niveau nécessaires à la création et à la lecture de cartes, ainsi qu'à la création de sondes, de tracepoints et de tampons de performances.
Associer des programmes à des points de trace
Les programmes de points de trace sont chargés automatiquement au démarrage. Une fois le programme de points de trace chargé, il doit être activé en procédant comme suit :
- Appelez
bpf_obj_get()pour obtenir le programmefdà partir de l'emplacement du fichier épinglé. Pour en savoir plus, consultez Fichiers disponibles dans sysfs. - Appelez
bpf_attach_tracepoint()dans la bibliothèque BPF, en lui transmettant le programmefdet le nom du point de trace.
L'exemple de code suivant montre comment associer le point de trace sched_switch défini dans le fichier source myschedtp.c précédent (la vérification des erreurs n'est pas affichée) :
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));
Lire à partir des cartes
Les cartes BPF sont compatibles avec des structures ou des types de clés et de valeurs complexes arbitraires. La bibliothèque Android BPF inclut une classe android::BpfMap qui utilise des modèles C++ pour instancier BpfMap en fonction du type de clé et de valeur pour la carte en question. L'exemple de code précédent montre comment utiliser une BpfMap avec une clé et une valeur sous forme d'entiers. Les entiers peuvent également être des structures arbitraires.
La classe BpfMap basée sur un modèle vous permet donc de définir un objet BpfMap personnalisé adapté à la carte en question. La carte est ensuite accessible à l'aide des fonctions générées sur mesure, qui sont sensibles aux types, ce qui permet d'obtenir un code plus propre.
Pour en savoir plus sur BpfMap, consultez les sources Android.
Faire du débogage
Au moment du démarrage, plusieurs messages liés au chargement de BPF sont enregistrés. Si le processus de chargement échoue pour une raison quelconque, un message de journal détaillé est fourni dans logcat. Filtrer les journaux logcat par bpf permet d'imprimer tous les messages et toutes les erreurs détaillées lors du chargement, telles que les erreurs du vérificateur eBPF.
Exemples d'eBPF dans Android
Les programmes suivants de l'AOSP fournissent d'autres exemples d'utilisation d'eBPF :
Le programme eBPF C
netdest utilisé par le daemon de mise en réseau (netd) dans Android à diverses fins, telles que le filtrage des sockets et la collecte de statistiques. Pour voir comment ce programme est utilisé, consultez les sources du moniteur de trafic eBPF.Le programme C eBPF
time_in_statecalcule le temps passé par une application Android à différentes fréquences de processeur, ce qui permet de calculer la consommation d'énergie.Dans Android 12, le programme eBPF C
gpu_memsuit l'utilisation totale de la mémoire GPU pour chaque processus et pour l'ensemble du système. Ce programme est utilisé pour le profilage de la mémoire GPU.