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 connectés à des sondes ou à des événements dans le noyau et utilisés pour collecter des statistiques utiles sur le noyau, le surveiller et le déboguer. 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 au format eBPF à l'aide d'une syntaxe de fichier de compilation simple décrite dans ce document.
Pour en savoir plus sur les composants internes 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 démarrage.
Chargeur Android BPF
Lors du démarrage d'Android, tous les programmes eBPF situés dans /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 et 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 license
Où :
name_of_my_map
est le nom de votre variable de carte. Ce nom indique au chargeur BPF le type de carte à créer et les paramètres à utiliser. Cette définition de struct est fournie par l'en-têtebpf_helpers.h
inclus.PROGTYPE/PROGNAME
représente le type et le nom du programme. Le type du programme peut être l'un de ceux listés dans le tableau suivant. Lorsqu'un type de programme n'est pas répertorié, il n'existe aucune convention d'attribution de noms stricte. Le nom doit simplement être connu du processus qui associe le programme.PROGFUNC
est une fonction qui, lors de la compilation, est placée dans une section du fichier généré.
kprobe | Relie PROGFUNC à une instruction de noyau à l'aide de l'infrastructure kprobe. PROGNAME doit être le nom de la fonction du kernel qui est kprobed. Reportez-vous à la documentation du noyau kprobe pour en savoir plus sur kprobes.
|
---|---|
point de trace | Accroche PROGFUNC à un point de trace. PROGNAME doit être au format SUBSYSTEM/EVENT . Par exemple, une section de point de trace pour associer des fonctions aux événements de changement de contexte du planificateur serait SEC("tracepoint/sched/sched_switch") , où sched est le nom du sous-système de suivi et sched_switch est le nom de l'événement de suivi. Pour en savoir plus sur les points de trace, consultez la documentation du kernel sur les é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. |
Vous trouverez d'autres types dans le code source du composant Loader.
Par exemple, le programme myschedtp.c
suivant ajoute des informations sur le dernier PID de tâche exécuté sur un processeur particulier. Ce programme atteint son objectif en créant une carte et en définissant une fonction tp_sched_switch
pouvant être associée à l'événement de traçage sched:sched_switch
. Pour en savoir plus, consultez la section 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 kernel lorsque le programme utilise des fonctions d'assistance BPF fournies par le kernel. Spécifiez le nom de la licence de votre programme sous la forme d'une chaîne, par exemple LICENSE("GPL")
ou LICENSE("Apache 2.0")
.
Format du fichier Android.bp
Pour que le système de compilation Android crée un programme .c
eBPF, vous devez créer une entrée dans le fichier Android.bp
du projet. Par exemple, pour créer un programme eBPF C 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
PROGNAME
soit le nom du programme et queFILENAME
soit le 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 tracepoint
sched_switch
précédent dansmyschedtp.c
, un fichier de programme est créé et épinglé à/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
.Pour toutes les cartes créées, en supposant que
MAPNAME
est le nom de la carte etFILENAME
est le 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_switch
précédent dansmyschedtp.c
, un fichier de carte 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, telles que la lecture de cartes ou l'association d'un programme à un point de trace.
Bibliothèque Android BPF
La bibliothèque BPF Android 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 pour créer et lire des cartes, créer des sondes, des tracepoints et des tampons de performances.
Associer des programmes à des points de trace
Les programmes Tracepoint sont chargés automatiquement au démarrage. Une fois le programme de tracepoint chargé, vous devez l'activer 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 la section Fichiers disponibles dans sysfs. - Appelez
bpf_attach_tracepoint()
dans la bibliothèque BPF, en lui transmettant le programmefd
et 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 mappages BPF acceptent des structures ou types de clés et de valeurs complexes arbitraires. La bibliothèque BPF Android inclut une classe android::BpfMap
qui utilise des modèles C++ pour instancier BpfMap
en fonction du type de clé et de valeur de 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.
Ainsi, la classe BpfMap
modélisée vous permet de définir un objet BpfMap
personnalisé adapté à la carte en question. Vous pouvez ensuite accéder à la carte à l'aide des fonctions générées par le biais de la génération personnalisée, qui sont sensibles au type, ce qui permet d'obtenir un code plus propre.
Pour en savoir plus sur BpfMap
, consultez les sources Android.
Problèmes de débogage
Au démarrage, plusieurs messages liés au chargement de BPF sont consigné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
affiche tous les messages et toutes les erreurs détaillées au moment du chargement, telles que les erreurs de vérificateur eBPF.
Exemples d'eBPF sur Android
Les programmes suivants d'AOSP fournissent d'autres exemples d'utilisation d'eBPF:
Le programme eBPF C
netd
est utilisé par le daemon de mise en réseau (netd) dans Android à diverses fins, telles que le filtrage de 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_state
calcule le temps qu'une application Android passe à différentes fréquences de processeur, qui est utilisé pour calculer la puissance.Dans Android 12, le programme eBPF C
gpu_mem
suit l'utilisation totale de la mémoire GPU pour chaque processus et l'ensemble du système. Ce programme est utilisé pour le profilage de la mémoire GPU.