O Extended Berkeley Packet Filter (eBPF) é uma máquina virtual no kernel que
executa programas eBPF fornecidos pelo usuário para estender a funcionalidade do kernel. Esses programas
podem ser conectados a sondas ou eventos no kernel e usados para coletar estatísticas
úteis, monitorar e depurar. Um programa é
carregado no kernel usando a syscall bpf(2)
e é fornecido pelo usuário
como um blob binário de instruções de máquina eBPF. O sistema de build do Android oferece
suporte à compilação de programas
C para eBPF usando a sintaxe de arquivo de build simples descrita neste documento.
Mais informações sobre os componentes internos e a arquitetura do eBPF podem ser encontradas na página eBPF de Brendan Gregg (em inglês).
O Android inclui um carregador eBPF e uma biblioteca que carregam programas eBPF no tempo de inicialização.
Loader BPF do Android
Durante a inicialização do Android, todos os programas eBPF localizados em /system/etc/bpf/
são
carregados. Esses programas são objetos binários criados pelo sistema de build do Android
a partir de programas C e são acompanhados por arquivos Android.bp
na árvore de origem
do Android. O sistema de build armazena os objetos gerados em /system/etc/bpf
, e
esses objetos se tornam parte da imagem do sistema.
Formato de um programa eBPF C do Android
Um programa C eBPF precisa ter o seguinte 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
Em que:
name_of_my_map
é o nome da variável do seu mapa. Esse nome informa ao carregador de BPF o tipo de mapa a ser criado e com quais parâmetros. Essa definição de struct é fornecida pelo cabeçalhobpf_helpers.h
incluído.PROGTYPE/PROGNAME
representa o tipo e o nome do programa. O tipo do programa pode ser qualquer um dos listados na tabela a seguir. Quando um tipo de programa não está listado, não há uma convenção de nomenclatura rígida para ele. O nome só precisa ser conhecido pelo processo que anexa o programa.PROGFUNC
é uma função que, quando compilada, é colocada em uma seção do arquivo resultante.
kprobe | O PROGFUNC é vinculado a uma instrução do kernel usando a
infraestrutura do kprobe. PROGNAME precisa ser o nome da função
do kernel que está sendo kprobed. Consulte a documentação do kernel kprobe para mais informações sobre
kprobes.
|
---|---|
ponto de rastreamento | Conecte o PROGFUNC a um tracepoint. PROGNAME precisa estar no formato SUBSYSTEM/EVENT . Por exemplo, uma seção de tracepoint
para anexar funções a eventos de alternância de contexto do programador seria
SEC("tracepoint/sched/sched_switch") , em que sched é
o nome do subsistema de rastreamento e sched_switch é o nome
do evento de rastreamento. Consulte a documentação do kernel de eventos de
rastreamento para mais informações sobre pontos de rastreamento.
|
skfilter | O programa funciona como um filtro de soquete de rede. |
schedcls | O programa funciona como um classificador de tráfego de rede. |
cgroupskb, cgroupsock | O programa é executado sempre que processos em um CGroup criam um soquete AF_INET ou AF_INET6. |
Outros tipos podem ser encontrados no código-fonte do carregador.
Por exemplo, o programa myschedtp.c
a seguir adiciona informações sobre o
PID da tarefa mais recente executada em uma CPU específica. Esse programa alcança o objetivo
criando um mapa e definindo uma função tp_sched_switch
que pode ser
anexada ao evento de rastreamento sched:sched_switch
. Para mais informações, consulte
Como anexar programas a pontos de rastreamento.
#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");
A macro LICENSE é usada para verificar se o programa é compatível com a licença do kernel quando o programa faz uso de funções auxiliares BPF fornecidas pelo kernel. Especifique o nome da licença do programa em formato de string, como
LICENSE("GPL")
ou LICENSE("Apache 2.0")
.
Formato do arquivo Android.bp
Para que o sistema de build do Android crie um programa .c
do eBPF, é necessário
criar uma entrada no arquivo Android.bp
do projeto. Por exemplo, para
criar um programa C eBPF chamado bpf_test.c
, faça a seguinte
entrada no arquivo Android.bp
do projeto:
bpf { name: "bpf_test.o", srcs: ["bpf_test.c"], cflags: [ "-Wall", "-Werror", ], }
Essa entrada compila o programa em C, resultando no objeto
/system/etc/bpf/bpf_test.o
. Na inicialização, o sistema Android carrega automaticamente
o programa bpf_test.o
no kernel.
Arquivos disponíveis no sysfs
Durante a inicialização, o sistema Android carrega automaticamente todos os objetos eBPF de
/system/etc/bpf/
, cria os mapas necessários para o programa e fixa o programa
carregado com os mapas no sistema de arquivos BPF. Esses arquivos podem ser usados para
interação adicional com o programa eBPF ou leitura de mapas. Nesta seção, descrevemos as convenções usadas para nomear esses arquivos e os locais deles no sysfs.
Os seguintes arquivos são criados e fixados:
Para todos os programas carregados, supondo que
PROGNAME
seja o nome do programa eFILENAME
seja o nome do arquivo C eBPF, o carregador do Android cria e fixa cada programa em/sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME
.Por exemplo, no exemplo anterior de ponto de rastreamento
sched_switch
emmyschedtp.c
, um arquivo de programa é criado e fixado em/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch
.Para todos os mapas criados, supondo que
MAPNAME
seja o nome do mapa eFILENAME
seja o nome do arquivo C eBPF, o carregador do Android cria e fixa cada mapa em/sys/fs/bpf/map_FILENAME_MAPNAME
.Por exemplo, no exemplo anterior de ponto de rastreamento
sched_switch
emmyschedtp.c
, um arquivo de mapa é criado e fixado em/sys/fs/bpf/map_myschedtp_cpu_pid_map
.bpf_obj_get()
na biblioteca BPF do Android retorna um descritor de arquivo do arquivo/sys/fs/bpf
fixado. Esse descritor de arquivo pode ser usado para outras operações, como ler mapas ou anexar um programa a um ponto de rastreamento.
Biblioteca BPF do Android
A biblioteca BPF do Android é chamada de libbpf_android.so
e faz parte da imagem
do sistema. Essa biblioteca oferece ao usuário os recursos de eBPF de baixo nível necessários
para criar e ler mapas, criar sondas, pontos de rastreamento e buffers de desempenho.
Anexar programas a pontos de rastreamento
Os programas de ponto de rastreamento são carregados automaticamente na inicialização. Após o carregamento, o programa de ponto de rastreamento precisa ser ativado seguindo estas etapas:
- Chame
bpf_obj_get()
para receber ofd
do programa do local do arquivo fixado. Para mais informações, consulte os Arquivos disponíveis no sysfs. - Chame
bpf_attach_tracepoint()
na biblioteca BPF, transmitindo o programafd
e o nome do ponto de rastreamento.
O exemplo de código a seguir mostra como anexar o ponto de rastreamento sched_switch
definido no arquivo de origem myschedtp.c
anterior (a verificação de erros não é mostrada):
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));
Ler os mapas
Os mapas BPF são compatíveis com estruturas ou tipos complexos arbitrários de chave e valor. A biblioteca BPF do Android inclui uma classe android::BpfMap
que usa modelos de C++ para instanciar BpfMap
com base na chave e no tipo de valor do mapa em questão. O exemplo de código anterior demonstra o uso de um BpfMap
com chave
e valor como números inteiros. Os números inteiros também podem ser estruturas arbitrárias.
Assim, a classe BpfMap
com modelo permite definir um objeto BpfMap
personalizado adequado para o mapa específico. O mapa pode ser acessado usando as
funções geradas de forma personalizada, que são sensíveis ao tipo, resultando em um código mais limpo.
Para mais informações sobre BpfMap
, consulte as
fontes do Android.
Depurar problemas
Durante a inicialização, várias mensagens relacionadas ao carregamento do BPF são registradas. Se o
processo de carregamento falhar por qualquer motivo, uma mensagem de registro detalhada será fornecida
no logcat. A filtragem dos registros do logcat por bpf
imprime todas as mensagens e
erros detalhados durante o tempo de carregamento, como erros do verificador eBPF.
Exemplos de eBPF no Android
Os seguintes programas no AOSP oferecem outros exemplos de uso do eBPF:
O programa eBPF C
netd
é usado pelo daemon de rede (netd) no Android para várias finalidades, como filtragem de soquetes e coleta de estatísticas. Para saber como esse programa é usado, confira as origens do monitor de tráfego eBPF.O programa eBPF C
time_in_state
calcula a quantidade de tempo que um app Android passa em diferentes frequências de CPU, que é usada para calcular a potência.No Android 12, o
gpu_mem
programa C eBPF monitora o uso total da memória da GPU para cada processo e para todo o sistema. Esse programa é usado para criar o perfil de memória da GPU.